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.
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.
components/email-winback.tsxnpx hoverlab add email-winback
Or over MCP, from your editor's agent — no account needed.
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.
Start a page with this section — add more, order them, and leave with the page source.
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
/**
* <EmailWinback> — a re-engagement email to a dormant account, as HTML that
* survives Outlook.
*
* The layout problem specific to a win-back: the reader has already decided
* this product is not worth their time, so the email cannot open by asking
* for more of it. It opens with the ONE reason they might be wrong — a
* fact about their own account, not a feature list — and asks for the
* smallest possible action: a single question with two answers, not a
* click straight back into a product they stopped opening. A "yes, still
* interested" that costs one tap is the whole design, because a win-back
* that leads with the upsell is indistinguishable from the promo that
* follows it into the same folder.
*
* 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 UNSUBSCRIBE LINK IS NOT SMALLER OR GREYER THAN USUAL. Sending to a
* dormant list is exactly the send most likely to be marked as spam rather
* than unsubscribed from, which costs sender reputation for every other
* email a mailbox provider decides whether to deliver. Making the opt-out
* easy to find is a deliverability decision here, not only a legal one.
*/
const BRAND = 'Acme'
const SITE_URL = 'https://acme.com'
const ACCENT = '#6366f1'
const POSTAL_ADDRESS = '548 Market Street, PMB 12345, San Francisco, CA 94104'
const LAST_ACTIVE = '4 months ago'
const SUBJECT = 'Your 1,204 saved items are still there'
const PREHEADER = "We have not changed anything without telling you — here's what's waiting."
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 style="padding: 32px 32px 0;">
<div style="font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 18px; font-weight: 700; color: #18181b;">
${BRAND}
</div>
</td>
</tr>
<tr>
<td style="padding: 24px 32px 0;">
<h1 style="margin: 0; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 22px; line-height: 1.35; font-weight: 700; color: #18181b;">
Your 1,204 saved items are still there
</h1>
<p style="margin: 12px 0 0; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 15px; line-height: 1.6; color: #52525b;">
You have not opened ${BRAND} since ${LAST_ACTIVE}, and we have not deleted anything in the meantime. Before your account moves to our free tier's storage limit, we wanted to ask a smaller question than "come back": is this still useful to you at all?
</p>
</td>
</tr>
<tr>
<td style="padding: 24px 32px 0;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #e4e4e7; border-radius: 10px;">
<tr>
<td style="padding: 16px 20px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="50%" align="center" bgcolor="${ACCENT}" style="border-radius: 8px;">
<a href="${SITE_URL}/login?ref=winback-yes"
style="display: block; padding: 11px 16px; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 14px; font-weight: 600; color: #ffffff; text-decoration: none; border-radius: 8px;">
Still interested
</a>
</td>
<td width="12"></td>
<td width="50%" align="center" style="border: 1px solid #e4e4e7; border-radius: 8px;">
<a href="${SITE_URL}/feedback/churn?ref=winback-no"
style="display: block; padding: 11px 16px; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 14px; font-weight: 600; color: #52525b; text-decoration: none; border-radius: 8px;">
Not anymore
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="padding: 20px 32px 0;">
<p style="margin: 0; font-family: -apple-system, 'Segoe UI', sans-serif; font-size: 13px; line-height: 1.6; color: #71717a;">
Either answer is useful to us, and neither commits you to anything today. If it was something specific that did not work, "not anymore" takes you to a two-question form — we read every one.
</p>
</td>
</tr>
<tr>
<td style="padding: 24px 32px 32px;">
<div style="border-top: 1px solid #e4e4e7; 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 your ${BRAND} account has been inactive for a while.
<a href="${SITE_URL}/preferences" style="color: #71717a; font-weight: 600;">Update your preferences</a>
or
<a href="${SITE_URL}/unsubscribe?list=winback" style="color: #71717a; font-weight: 600;">unsubscribe from these emails</a>.
<br /><br />
${BRAND}, ${POSTAL_ADDRESS}
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`
}
export function EmailWinback() {
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-[600px] w-full rounded-xl border border-border/60 bg-white"
/>
</div>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/email-winback.tsx and import it where you need the section:
import { EmailWinback } from '@/components/email-winback'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.
<!--
Re-Engagement 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> Your 1,204 saved items are still there</span>
<span class="text-muted-foreground/80">We have not changed anything without telling you — here's what's waiting.</span>
</div>
<iframe title="Your 1,204 saved items are still there — email preview" srcDoc="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot; /&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
&lt;title&gt;Your 1,204 saved items are still there&lt;/title&gt;
&lt;/head&gt;
&lt;body style=&quot;margin: 0; padding: 0; background-color: #f4f4f5;&quot;&gt;
&lt;div style=&quot;display: none; max-height: 0; overflow: hidden; opacity: 0;&quot;&gt;
We have not changed anything without telling you — here&#x27;s what&#x27;s waiting.
&lt;/div&gt;
&lt;table role=&quot;presentation&quot; width=&quot;100%&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; border=&quot;0&quot; style=&quot;background-color: #f4f4f5;&quot;&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot; style=&quot;padding: 32px 16px;&quot;&gt;
&lt;table role=&quot;presentation&quot; width=&quot;600&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; border=&quot;0&quot; style=&quot;width: 600px; max-width: 100%; background-color: #ffffff; border-radius: 12px; overflow: hidden;&quot;&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 32px 32px 0;&quot;&gt;
&lt;div style=&quot;font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 18px; font-weight: 700; color: #18181b;&quot;&gt;
Acme
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 24px 32px 0;&quot;&gt;
&lt;h1 style=&quot;margin: 0; font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 22px; line-height: 1.35; font-weight: 700; color: #18181b;&quot;&gt;
Your 1,204 saved items are still there
&lt;/h1&gt;
&lt;p style=&quot;margin: 12px 0 0; font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 15px; line-height: 1.6; color: #52525b;&quot;&gt;
You have not opened Acme since 4 months ago, and we have not deleted anything in the meantime. Before your account moves to our free tier&#x27;s storage limit, we wanted to ask a smaller question than &quot;come back&quot;: is this still useful to you at all?
&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 24px 32px 0;&quot;&gt;
&lt;table role=&quot;presentation&quot; width=&quot;100%&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; border=&quot;0&quot; style=&quot;border: 1px solid #e4e4e7; border-radius: 10px;&quot;&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 16px 20px;&quot;&gt;
&lt;table role=&quot;presentation&quot; width=&quot;100%&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; border=&quot;0&quot;&gt;
&lt;tr&gt;
&lt;td width=&quot;50%&quot; align=&quot;center&quot; bgcolor=&quot;#6366f1&quot; style=&quot;border-radius: 8px;&quot;&gt;
&lt;a href=&quot;https://acme.com/login?ref=winback-yes&quot;
style=&quot;display: block; padding: 11px 16px; font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 14px; font-weight: 600; color: #ffffff; text-decoration: none; border-radius: 8px;&quot;&gt;
Still interested
&lt;/a&gt;
&lt;/td&gt;
&lt;td width=&quot;12&quot;&gt;&lt;/td&gt;
&lt;td width=&quot;50%&quot; align=&quot;center&quot; style=&quot;border: 1px solid #e4e4e7; border-radius: 8px;&quot;&gt;
&lt;a href=&quot;https://acme.com/feedback/churn?ref=winback-no&quot;
style=&quot;display: block; padding: 11px 16px; font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 14px; font-weight: 600; color: #52525b; text-decoration: none; border-radius: 8px;&quot;&gt;
Not anymore
&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 20px 32px 0;&quot;&gt;
&lt;p style=&quot;margin: 0; font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 13px; line-height: 1.6; color: #71717a;&quot;&gt;
Either answer is useful to us, and neither commits you to anything today. If it was something specific that did not work, &quot;not anymore&quot; takes you to a two-question form — we read every one.
&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 24px 32px 32px;&quot;&gt;
&lt;div style=&quot;border-top: 1px solid #e4e4e7; padding-top: 16px; font-family: -apple-system, &#x27;Segoe UI&#x27;, sans-serif; font-size: 12px; line-height: 1.6; color: #a1a1aa;&quot;&gt;
You are getting this because your Acme account has been inactive for a while.
&lt;a href=&quot;https://acme.com/preferences&quot; style=&quot;color: #71717a; font-weight: 600;&quot;&gt;Update your preferences&lt;/a&gt;
&amp;nbsp;or&amp;nbsp;
&lt;a href=&quot;https://acme.com/unsubscribe?list=winback&quot; style=&quot;color: #71717a; font-weight: 600;&quot;&gt;unsubscribe from these emails&lt;/a&gt;.
&lt;br /&gt;&lt;br /&gt;
Acme, 548 Market Street, PMB 12345, San Francisco, CA 94104
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;" sandbox="" class="h-[600px] w-full rounded-xl border border-border/60 bg-white"></iframe>
</div>
What each framework gets across the whole catalog — effects convert properly; this rung is markup.
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.
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 TemplatesCopying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.
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.
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.
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.