Skip to content
Marketing block

Promotional Offer Email

A time-boxed discount email that leads with the number, states a deadline rather than a live countdown email cannot run, and letter-spaces the code so it survives being copied out of an inbox.

155 linesNo dependencies
  • email
  • promo
  • discount
  • sale
  • html email

What's included

  • components/email-promo-offer.tsx
  • No runtime dependencies

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add email-promo-offer

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Start a page with this section — add more, order them, and leave with the page source.

Preview

Subject: 20% off Pro, through the end of the monthUse SAVE20 at checkout — ends 30 september, 11:59pm your time.

Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.

Source

components/email-promo-offer.tsx
/**
 * <EmailPromoOffer> — a time-boxed discount email, as HTML that survives Outlook.
 *
 * The layout problem specific to a promo: a single number has to read as
 * the whole email from an inbox thumbnail, because that is genuinely most
 * of what decides whether it gets opened. So the discount sits alone at
 * the top in oversized type, before the headline that explains it — the
 * opposite reading order from `email-product-announcement`, where the
 * headline comes first and the detail earns the click.
 *
 * A discount code is set in a MONOSPACE font-family and letter-spaced, so
 * `S4V3-20` cannot be misread as `54V3-2O` when it is copied out of an
 * inbox rather than clicked from the button beside it — the two ways a
 * recipient actually uses one.
 *
 * See `email-product-announcement.tsx` for why the markup is nested
 * `<table role="presentation">`s and inline styles rather than flexbox and
 * a `<style>` block, why the footer carries a postal address and an
 * unsubscribe link, and why the preview is a sandboxed iframe rather than a
 * `<table>` rendered as JSX on this page.
 *
 * THE EXPIRY IS A DATE STRING, NOT A LIVE COUNTDOWN. Email has no
 * JavaScript to update a timer after delivery, and most clients cache
 * images — a countdown gif frozen at "3 days left" a week after send is a
 * dishonest email, not an urgent one. State the deadline and let the
 * reader do the subtraction.
 */

const BRAND = 'Acme'
const SITE_URL = 'https://acme.com'
const ACCENT = '#dc2626'
const POSTAL_ADDRESS = '548 Market Street, PMB 12345, San Francisco, CA 94104'
const CODE = 'SAVE20'
const EXPIRES = 'Ends 30 September, 11:59pm your time'

const SUBJECT = '20% off Pro, through the end of the month'
const PREHEADER = `Use ${CODE} at checkout — ${EXPIRES.toLowerCase()}.`

function buildEmailHtml(): string {
  return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>${SUBJECT}</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f5;">
  <div style="display: none; max-height: 0; overflow: hidden; opacity: 0;">
    ${PREHEADER}
  </div>

  <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f4f4f5;">
    <tr>
      <td align="center" style="padding: 32px 16px;">

        <table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 600px; max-width: 100%; background-color: #ffffff; border-radius: 12px; overflow: hidden;">
          <tr>
            <td align="center" bgcolor="${ACCENT}" style="padding: 28px 32px;">
              <div style="font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 40px; font-weight: 800; letter-spacing: -0.01em; color: #ffffff;">
                20% off
              </div>
              <div style="margin-top: 4px; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 13px; font-weight: 600; color: #fecaca;">
                ${EXPIRES}
              </div>
            </td>
          </tr>

          <tr>
            <td style="padding: 28px 32px 0;">
              <h1 style="margin: 0; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 22px; line-height: 1.3; font-weight: 700; color: #18181b;">
                One month left on your Pro trial. Make it permanent for less.
              </h1>
              <p style="margin: 12px 0 0; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 15px; line-height: 1.6; color: #52525b;">
                This is the price we send once a year, and only to accounts that already know what Pro does for them. Apply it at checkout — it stacks with nothing else, so there is no better time to switch off the free plan.
              </p>
            </td>
          </tr>

          <tr>
            <td style="padding: 20px 32px 0;">
              <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px dashed #d4d4d8; border-radius: 10px;">
                <tr>
                  <td align="center" style="padding: 16px;">
                    <div style="font-family: 'SFMono-Regular', Consolas, monospace; font-size: 20px; font-weight: 700; letter-spacing: 0.08em; color: #18181b;">
                      ${CODE}
                    </div>
                    <div style="margin-top: 2px; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 12px; color: #a1a1aa;">
                      Applies automatically from the button below
                    </div>
                  </td>
                </tr>
              </table>
            </td>
          </tr>

          <tr>
            <td style="padding: 20px 32px 0;">
              <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
                <tr>
                  <td align="center" bgcolor="${ACCENT}" style="border-radius: 8px;">
                    <a href="${SITE_URL}/upgrade?code=${CODE}"
                       style="display: block; padding: 14px 24px; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 16px; font-weight: 700; color: #ffffff; text-decoration: none; border-radius: 8px;">
                      Upgrade to Pro — 20% off
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>

          <tr>
            <td style="padding: 16px 32px 32px;">
              <p style="margin: 0; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 12px; line-height: 1.6; color: #a1a1aa;">
                One redemption per account. Cannot be combined with another offer or applied retroactively to an existing annual plan.
              </p>
              <div style="border-top: 1px solid #e4e4e7; margin-top: 16px; padding-top: 16px; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 12px; line-height: 1.6; color: #a1a1aa;">
                You are getting this because you have an active ${BRAND} trial.
                <a href="${SITE_URL}/preferences" style="color: #71717a;">Update your preferences</a>
                &nbsp;or&nbsp;
                <a href="${SITE_URL}/unsubscribe?list=offers" style="color: #71717a;">unsubscribe</a>.
                <br /><br />
                ${BRAND}, ${POSTAL_ADDRESS}
              </div>
            </td>
          </tr>
        </table>

      </td>
    </tr>
  </table>
</body>
</html>`
}

export function EmailPromoOffer() {
  const html = buildEmailHtml()

  return (
    <div className="rounded-2xl border border-border/60 bg-card/60 p-4">
      <div className="mb-3 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 text-xs text-muted-foreground">
        <span>
          <span className="font-semibold text-foreground">Subject:</span> {SUBJECT}
        </span>
        <span className="text-muted-foreground/80">{PREHEADER}</span>
      </div>
      <iframe
        title={`${SUBJECT} — email preview`}
        srcDoc={html}
        sandbox=""
        className="h-[660px] w-full rounded-xl border border-border/60 bg-white"
      />
    </div>
  )
}

Before you paste

  • Styling is Tailwind utility classes on semantic tokens (bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.
  • Nothing to install. No component library, no icon package.
  • Every prop has a default, so it renders standalone before you wire it up.

Where it goes

Drop it at components/email-promo-offer.tsx and import it where you need the section:

import { EmailPromoOffer } from '@/components/email-promo-offer'

Not using React?

The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.

email-promo-offer.html
<!--
  Promotional Offer Email — markup from the Hoverlab catalog.

  This is the block rendered once to HTML and wrapped as a component
  file. It is not a port of the React source: the Tailwind classes carry
  the design, which is the part that took the work, and they are the same
  in every framework.

  This block has no interactive behaviour, so nothing is missing.
-->
<div class="rounded-2xl border border-border/60 bg-card/60 p-4">
  <div class="mb-3 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 text-xs text-muted-foreground">
    <span><span class="font-semibold text-foreground">Subject:</span> 20% off Pro, through the end of the month</span>
    <span class="text-muted-foreground/80">Use SAVE20 at checkout — ends 30 september, 11:59pm your time.</span>
  </div>
  <iframe title="20% off Pro, through the end of the month — email preview" srcDoc="&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1&amp;quot; /&amp;gt;
  &amp;lt;title&amp;gt;20% off Pro, through the end of the month&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body style=&amp;quot;margin: 0; padding: 0; background-color: #f4f4f5;&amp;quot;&amp;gt;
  &amp;lt;div style=&amp;quot;display: none; max-height: 0; overflow: hidden; opacity: 0;&amp;quot;&amp;gt;
    Use SAVE20 at checkout — ends 30 september, 11:59pm your time.
  &amp;lt;/div&amp;gt;

  &amp;lt;table role=&amp;quot;presentation&amp;quot; width=&amp;quot;100%&amp;quot; cellpadding=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; border=&amp;quot;0&amp;quot; style=&amp;quot;background-color: #f4f4f5;&amp;quot;&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td align=&amp;quot;center&amp;quot; style=&amp;quot;padding: 32px 16px;&amp;quot;&amp;gt;

        &amp;lt;table role=&amp;quot;presentation&amp;quot; width=&amp;quot;600&amp;quot; cellpadding=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; border=&amp;quot;0&amp;quot; style=&amp;quot;width: 600px; max-width: 100%; background-color: #ffffff; border-radius: 12px; overflow: hidden;&amp;quot;&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;td align=&amp;quot;center&amp;quot; bgcolor=&amp;quot;#dc2626&amp;quot; style=&amp;quot;padding: 28px 32px;&amp;quot;&amp;gt;
              &amp;lt;div style=&amp;quot;font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 40px; font-weight: 800; letter-spacing: -0.01em; color: #ffffff;&amp;quot;&amp;gt;
                20% off
              &amp;lt;/div&amp;gt;
              &amp;lt;div style=&amp;quot;margin-top: 4px; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 13px; font-weight: 600; color: #fecaca;&amp;quot;&amp;gt;
                Ends 30 September, 11:59pm your time
              &amp;lt;/div&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;

          &amp;lt;tr&amp;gt;
            &amp;lt;td style=&amp;quot;padding: 28px 32px 0;&amp;quot;&amp;gt;
              &amp;lt;h1 style=&amp;quot;margin: 0; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 22px; line-height: 1.3; font-weight: 700; color: #18181b;&amp;quot;&amp;gt;
                One month left on your Pro trial. Make it permanent for less.
              &amp;lt;/h1&amp;gt;
              &amp;lt;p style=&amp;quot;margin: 12px 0 0; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 15px; line-height: 1.6; color: #52525b;&amp;quot;&amp;gt;
                This is the price we send once a year, and only to accounts that already know what Pro does for them. Apply it at checkout — it stacks with nothing else, so there is no better time to switch off the free plan.
              &amp;lt;/p&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;

          &amp;lt;tr&amp;gt;
            &amp;lt;td style=&amp;quot;padding: 20px 32px 0;&amp;quot;&amp;gt;
              &amp;lt;table role=&amp;quot;presentation&amp;quot; width=&amp;quot;100%&amp;quot; cellpadding=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; border=&amp;quot;0&amp;quot; style=&amp;quot;border: 1px dashed #d4d4d8; border-radius: 10px;&amp;quot;&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;td align=&amp;quot;center&amp;quot; style=&amp;quot;padding: 16px;&amp;quot;&amp;gt;
                    &amp;lt;div style=&amp;quot;font-family: &amp;#x27;SFMono-Regular&amp;#x27;, Consolas, monospace; font-size: 20px; font-weight: 700; letter-spacing: 0.08em; color: #18181b;&amp;quot;&amp;gt;
                      SAVE20
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div style=&amp;quot;margin-top: 2px; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 12px; color: #a1a1aa;&amp;quot;&amp;gt;
                      Applies automatically from the button below
                    &amp;lt;/div&amp;gt;
                  &amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
              &amp;lt;/table&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;

          &amp;lt;tr&amp;gt;
            &amp;lt;td style=&amp;quot;padding: 20px 32px 0;&amp;quot;&amp;gt;
              &amp;lt;table role=&amp;quot;presentation&amp;quot; cellpadding=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; border=&amp;quot;0&amp;quot; width=&amp;quot;100%&amp;quot;&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;td align=&amp;quot;center&amp;quot; bgcolor=&amp;quot;#dc2626&amp;quot; style=&amp;quot;border-radius: 8px;&amp;quot;&amp;gt;
                    &amp;lt;a href=&amp;quot;https://acme.com/upgrade?code=SAVE20&amp;quot;
                       style=&amp;quot;display: block; padding: 14px 24px; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 16px; font-weight: 700; color: #ffffff; text-decoration: none; border-radius: 8px;&amp;quot;&amp;gt;
                      Upgrade to Pro — 20% off
                    &amp;lt;/a&amp;gt;
                  &amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
              &amp;lt;/table&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;

          &amp;lt;tr&amp;gt;
            &amp;lt;td style=&amp;quot;padding: 16px 32px 32px;&amp;quot;&amp;gt;
              &amp;lt;p style=&amp;quot;margin: 0; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 12px; line-height: 1.6; color: #a1a1aa;&amp;quot;&amp;gt;
                One redemption per account. Cannot be combined with another offer or applied retroactively to an existing annual plan.
              &amp;lt;/p&amp;gt;
              &amp;lt;div style=&amp;quot;border-top: 1px solid #e4e4e7; margin-top: 16px; padding-top: 16px; font-family: -apple-system, &amp;#x27;Segoe UI&amp;#x27;, sans-serif; font-size: 12px; line-height: 1.6; color: #a1a1aa;&amp;quot;&amp;gt;
                You are getting this because you have an active Acme trial.
                &amp;lt;a href=&amp;quot;https://acme.com/preferences&amp;quot; style=&amp;quot;color: #71717a;&amp;quot;&amp;gt;Update your preferences&amp;lt;/a&amp;gt;
                &amp;amp;nbsp;or&amp;amp;nbsp;
                &amp;lt;a href=&amp;quot;https://acme.com/unsubscribe?list=offers&amp;quot; style=&amp;quot;color: #71717a;&amp;quot;&amp;gt;unsubscribe&amp;lt;/a&amp;gt;.
                &amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
                Acme, 548 Market Street, PMB 12345, San Francisco, CA 94104
              &amp;lt;/div&amp;gt;
            &amp;lt;/td&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/table&amp;gt;

      &amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/table&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;" sandbox="" class="h-[660px] w-full rounded-xl border border-border/60 bg-white"></iframe>
</div>
  • This is rendered HTML, not a translation of the React source. The Tailwind classes carry the design and work in any framework.
  • It is one frame: the component in its initial state, with no props applied beyond the defaults.
  • Requires Tailwind, and the design tokens the classes reference (bg-card, text-muted-foreground, and so on). The template ZIPs ship a globals.css that defines them.

What each framework gets across the whole catalog — effects convert properly; this rung is markup.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

3 more blocks in Email Templates

All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.

Browse Email Templates

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once

More Email Templates blocks

View category
Open the full page for this block

Feature Launch Email

A feature-announcement email as HTML that survives Outlook — nested tables, inline styles and a hidden preheader, previewed in a sandboxed iframe so the styles cannot leak onto the page around it.

Email Templates173 linesNo deps
Open the full page for this block

Newsletter Digest Email

A periodic roundup of several stories in one email, each reduced to a kicker, a headline and one line — the shape a reader can scan in order instead of a wall of paragraphs.

Email Templates162 linesNo deps
Open the full page for this block

Re-Engagement Email

A win-back email to a dormant account that opens with a fact about their own data, not a feature list, and asks for one tap instead of a click back into a product they stopped opening.

Email Templates151 linesNo deps