Template galleries show verification emails you cannot paste, and email builders hand you a layout with no verification words. This email verification email template is both: 7 parts of copy that say who sent it, why, what to click, when it expires and what to do if it was not you, in table-based HTML with a plain-text twin.

The email verification email template: the copy, the HTML and the plain text

An email verification email template needs 7 parts: a subject with the action and the app name, a preheader, one sentence saying who is writing and why, a single button, the full link as text, the expiry and single-use line, and a not-you line with a monitored address above the legal sender name.

The message is one control of several that decide whether your app’s mail gets read at all; the others belong to how to improve email deliverability. Here the job is narrower: the words, the markup, and the page the button opens.

This verification email template is free to use, with no attribution and nothing to download: copy the three blocks below into your provider or your codebase. The placeholders use one style throughout, double braces, so a search for them later finds every one.

Subject:    Confirm your email for {{app_name}}
Preheader:  One click to finish setting up your {{app_name}} account.
Reason:     You're getting this because someone signed up for {{app_name}}
            with this address. Confirm it's you so we can reach you about
            your account.
Button:     Confirm email address   ->  {{verify_url}}
Fallback:   Button not working? Paste this link into your browser:
            {{verify_url}}
Expiry:     This link expires in {{expiry}} and works once. If it has
            expired, open {{app_name}} and ask for a new one.
Not you:    Didn't sign up? Ignore this email and the address will not be
            confirmed. Questions: {{support_email}}
Footer:     Sent by {{legal_name}} for {{app_name}}.

The not-you line and the footer count as one part, which is why eight labels make seven parts. When you write a verification email from this block, change the placeholders and your app’s facts and leave the order alone.

The placeholders come from the flow that creates the link. The token, the endpoint and the expiry are decided when you work out how to send a verification email, and this page never picks the expiry for you: {{expiry}} is whatever your flow sets, written in words a person reads, not as a count of seconds.

Every placeholder has to be filled at send time, because an unfilled one is exactly what the customer reads. In one of the apps I audited in June and July 2026, a CRM’s email templates stored raw {{name}} placeholders when a template was picked before the contact, and signed every email “Your Name” from “Your Company”. Only a real send, read by someone who has never seen the template, catches that, which is why check 5 below searches the raw message for leftover braces.

On managed auth, paste the HTML into the provider’s template editor and swap the placeholders for the provider’s own variables. On a hosted Supabase project that is the Email Templates page in the dashboard, where {{ .ConfirmationURL }} holds the confirmation link and {{ .Token }} holds a 6-digit one-time password that can be used instead of the link, per Supabase’s email template docs. Supabase’s variable list has no entry for the expiry, so type your project’s value into that line.

Verify email template HTML: tables, inline styles and a button that survives Outlook

Verification email HTML uses tables and inline styles because classic Outlook for Windows renders mail with Word’s engine. The safe shape I use is one column 600 pixels wide, system fonts, a button made from a padded table cell with a real link, 16-pixel text, and a plain-text part sent alongside it.

Microsoft’s note on Word rendering in Outlook says Outlook 2007 “uses the HTML parsing and rendering engine from Microsoft Office Word 2007 to display HTML message bodies”, and it lists float, position, max-width and background images as unsupported. Litmus’s guide to Outlook rendering (October 2023) says the Windows desktop versions of Outlook from 2007 to 2019 “use Word as the rendering engine” and that the Office 365 desktop app “uses Word as a rendering engine” as well, while the new Outlook for Windows “doesn’t use Microsoft Word as a rendering engine, but instead uses a web browser engine”. Microsoft describes that new app as “inspired by the Outlook web experience” and built on WebView2. So on this page, Outlook means the classic desktop app for Windows unless I name another one.

Gmail is less strict: Gmail’s CSS support page says you can style mail “using inline <style> blocks and standard CSS”. Can I email, which tests each feature per client, marks the <style> element as only partly supported in Gmail and in Outlook, so the block below keeps every style inline. Like the copy, this HTML verification email template is free to paste and change.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Confirm your email for {{app_name}}</title>
</head>
<body style="margin:0; padding:0; background-color:#f4f4f5;">
  <!-- Outer table: full width, centers the column. role="presentation" marks it as layout. -->
  <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#f4f4f5;">
    <tr>
      <td align="center" style="padding:24px 12px;">
        <!-- The column: 600 px where max-width works; Outlook 2007 to 2016 for Windows ignores max-width and fills the pane. -->
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px; background-color:#ffffff;">
          <tr>
            <!-- Preheader: a visible first line that inbox lists show as the preview. Nothing is hidden. -->
            <td style="padding:24px 32px 0 32px; font-family:-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size:14px; line-height:20px; color:#4b5563;">
              One click to finish setting up your {{app_name}} account.
            </td>
          </tr>
          <tr>
            <td style="padding:16px 32px 0 32px; font-family:-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size:16px; line-height:24px; color:#1f2937;">
              <h1 style="margin:0 0 16px 0; font-size:22px; line-height:28px;">Confirm your email address</h1>
              <p style="margin:0 0 24px 0;">You're getting this because someone signed up for {{app_name}} with this address. Confirm it's you so we can reach you about your account.</p>
            </td>
          </tr>
          <tr>
            <td style="padding:0 32px;">
              <!-- The button: a padded table cell around a real link, so it works with images off. Square corners on purpose. -->
              <table role="presentation" cellpadding="0" cellspacing="0" border="0">
                <tr>
                  <td bgcolor="#1d4ed8" style="background-color:#1d4ed8; padding:14px 28px;">
                    <a href="{{verify_url}}" style="font-family:-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size:16px; font-weight:bold; color:#ffffff; text-decoration:none;">Confirm email address</a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
          <tr>
            <td style="padding:24px 32px 32px 32px; font-family:-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size:16px; line-height:24px; color:#1f2937;">
              <!-- Fallback link, expiry, not-you line, footer. -->
              <p style="margin:0 0 8px 0;">Button not working? Paste this link into your browser:</p>
              <p style="margin:0 0 24px 0;"><a href="{{verify_url}}" style="color:#1d4ed8;">{{verify_url}}</a></p>
              <p style="margin:0 0 16px 0;">This link expires in {{expiry}} and works once. If it has expired, open {{app_name}} and ask for a new one.</p>
              <p style="margin:0 0 24px 0;">Didn't sign up? Ignore this email and the address will not be confirmed. Questions: <a href="mailto:{{support_email}}" style="color:#1d4ed8;">{{support_email}}</a></p>
              <p style="margin:0; font-size:14px; line-height:20px; color:#4b5563;">Sent by {{legal_name}} for {{app_name}}.</p>
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
</html>

The build choices in that block are mine, one line each:

  • One column, which I cap at 600 pixels with max-width. Can I email marks max-width unsupported in classic Outlook for Windows 2007 to 2016, so there the column stretches to the reading pane and still reads.
  • System fonts, so nothing waits on a web font.
  • No image needed to understand the message. A logo is optional and gets alt text.
  • The button is a table cell with padding around a real href, because Can I email lists padding in Outlook for Windows as “Only supported on table cells”, and a link keeps working with images off.
  • Square corners. Can I email shows border-radius unsupported in Outlook for Windows from 2003 to 2019, so the block leaves it out rather than look different there.
  • The raw URL sits under the button as text, for the reader whose button fails.
  • A lang attribute on the html element and role="presentation" on layout tables, both of which Can I email lists as supported in Gmail and Apple Mail.
  • A visible preheader line. Gmail’s sender guidelines say “Don’t use HTML and CSS to hide content in your messages. Hiding content might cause messages to be marked as spam.”
  • Body text at 16 pixels, my default, and colors that meet the WCAG contrast minimum: “a contrast ratio of at least 4.5:1” for normal text. Each color pair in the block clears it.

Dark mode is the part people expect CSS to fix, and it mostly can’t. Can I email’s prefers-color-scheme table (last tested March 2023) shows the media query supported in Apple Mail and Outlook.com but not in Gmail or in Outlook for Windows, and its color-scheme meta tag page (tested September 2026) reads “Not supported in Outlook”. Gmail and classic Outlook are two of the clients in the checks below, so the block carries no dark-mode CSS. What I’d lean on instead: plain colors that survive a client’s own inversion, and no logo drawn in pure black on a transparent background, which disappears when the background turns dark.

The plain-text part goes out with the HTML in one multipart/alternative message. RFC 2046 defines each part as “an ‘alternative’ version of the same information”, in “an order of increasing faithfulness to the original content”, so the text part comes first and the HTML last. As I read it, a text-only client shows the text part, so the twin carries the same parts as the HTML:

One click to finish setting up your {{app_name}} account.

Confirm your email address

You're getting this because someone signed up for {{app_name}} with this
address. Confirm it's you so we can reach you about your account.

Confirm email address:
{{verify_url}}

This link expires in {{expiry}} and works once. If it has expired, open
{{app_name}} and ask for a new one.

Didn't sign up? Ignore this email and the address will not be confirmed.
Questions: {{support_email}}

Sent by {{legal_name}} for {{app_name}}.

For more layout than one column, an independent open-source responsive template is MIT-licensed and kept on a personal GitHub account, with its last commit in March 2024. Its README gives the same advice this page does: “you should always inline your CSS and send a test to yourself before sending.” MJML and React Email are vendor-maintained alternatives if you would rather generate the tables than write them. Where the finished HTML lives, in the provider or in your repo, sits with the rest of transactional email best practices.

How to fill it in: an email verification message example, part by part

A verification email message has seven parts, and each has a common mistake: a subject with no action, an empty preheader, no reason given, two buttons, a shortened link, no stated expiry, and a no-reply sender with no alternative. Each of the seven is fixed in the copy, not the code.

The table walks the template’s parts top to bottom; the mistake column is my take on what breaks each one, not a rule from a mail provider.

PartWhat goes thereThe mistake people make
SubjectThe action and the app name: “Confirm your email for {{app_name}}""Welcome!” with no action, or a line full of exclamation marks that reads as marketing
PreheaderThe second line inbox lists show under the subjectLeft empty, so the client previews whatever text comes first
Reason sentenceWho is writing and why this person got the messageAssuming they remember signing up
ButtonOne action, written as a verbTwo buttons, or a button that is an image
Fallback linkThe full URL as plain textA tracked or shortened link that looks like phishing
Expiry and single useHow long the link lasts and that it works once, using the flow’s real valueNo expiry stated, so the reader can’t tell why an old link fails
Not-you line and footerWhat to do if they didn’t sign up, a monitored address, the legal sender nameA no-reply address with nothing else to write to

The fallback row has outside support. Gmail’s email sender guidelines say “Web links in the message body should be visible and easy to understand. Recipients should know what to expect when they click a link.” A shortened URL hides the destination, and that is the opposite.

Tone: plain, with no marketing, no second call to action and no social icons, because a verification email is transactional. The same guidelines say “Don’t mix different types of content in the same message. For example, don’t include promotions in sales receipt messages.” A signup confirmation is the sales receipt of that sentence: it does one job.

What never goes in, as my rule: the password, any personal data beyond the address itself, and any link other than the verification endpoint and the support address.

A verification code email keeps the same seven parts with two swaps: the button becomes the code on its own line in large monospaced text, and the fallback link becomes a line saying the company will never ask for the code. I keep the code out of the subject because subjects show in lock-screen and notification previews.

Subject:    Your {{app_name}} sign-up code
Preheader:  Enter this code to finish setting up your account.
Reason:     You're getting this because someone signed up for {{app_name}}
            with this address. Enter this code on the sign-up screen:
Code:       {{code}}        (large monospaced text, own line, e.g. 123 456)
Never ask:  We will never ask for this code by phone or chat.
Expiry:     This code expires in {{expiry}} and works once.
Not you:    Didn't sign up? Ignore this email. Questions: {{support_email}}
Footer:     Sent by {{legal_name}} for {{app_name}}.

Group the digits for reading, as in the obvious dummy above, and put the same code in the plain-text part. The expiry is the flow’s real number of minutes. A link back to the app’s code entry screen is optional. For an OTP email template in HTML, I reuse the HTML block above with two changes: the button’s table cell holds {{code}} in a monospaced font at about 28 pixels instead of a link, and the fallback-link paragraph becomes the never-ask line. Two questions belong to the send path, not to the message: when a code beats a link (mail scanners that open links before the person does, or signup on a laptop with mail read on a phone), and the code’s length, expiry and attempt limit.

The three email verification examples below use one fictional app, Example Invoices, an invoicing tool with support at support@example.com. Its flow sets the expiry, and for these examples assume it is 1 hour, which is also Supabase’s default for magic links and one-time codes.

Example 1, a signup with a link, provides an example email confirmation message with every part filled in:

Subject: Confirm your email for Example Invoices
Preheader: One click to finish setting up your Example Invoices account.

You're getting this because someone signed up for Example Invoices with this
address. Confirm it's you so we can reach you about your account.

[ Confirm email address ]
Button not working? Paste this link: https://app.example.com/verify?token=...
This link expires in 1 hour and works once.
Didn't sign up? Ignore this email. Questions: support@example.com
Sent by Example Invoices.

Example 2, signup with a code:

Subject: Your Example Invoices sign-up code
Preheader: Enter this code to finish setting up your account.
You're getting this because someone signed up for Example Invoices with this
address. Enter this code on the sign-up screen:

    123 456

We will never ask for this code by phone or chat.
This code expires in 1 hour and works once.
Didn't sign up? Ignore this email. Questions: support@example.com
Sent by Example Invoices.

Example 3, the user changed their email address. Two messages go out: a confirmation to the new address, and a notice with no link to the old one. Both say which address is being confirmed and what happens to the old one. They are shortened to the lines that differ from example 1; the preheader, support line and footer stay as they were there.

To the new address
Subject: Confirm your new email for Example Invoices
Someone asked to move an Example Invoices account to this address. The
account's email changes only after you confirm.
[ Confirm new email address ]  https://app.example.com/confirm-change?token=...
This link expires in 1 hour and works once. Didn't ask? Ignore this email.

To the old address (no link)
Subject: Your Example Invoices email is being changed
Someone asked to move this account's email to n•••@example.com. If that was
you, there is nothing to do. If not, write to support@example.com now.

Why the third one matters, as I see it: without the notice to the old address, the owner of the account learns of a change only after it has happened, usually when a password reset goes somewhere else. On Supabase the old-address notice is the “Email address changed” security notification, and security emails “are only sent to users if the respective security notifications have been enabled at a project-level”.

A real case shows the gap. A GitHub security advisory for the Flowise project, published on November 12, 2025, describes the gap in its own words: the application “allows changing the account email address (used as a login identifier and/or password recovery address) without verifying the requester’s authority to make that change (no confirmation to the old email, no authentication step).” The advisory rates the impact as full account takeover and lists 3.0.10 as the patched version. My take: a change of address is itself a pair of messages, a confirmation to the new address and a notice to the old one, and each needs the same seven parts.

Team invites and magic-link login use the same seven parts with a different reason sentence. For real brands’ designs, the Loops and Really Good Emails collections are the places to browse.

Email verification page design covers five states across two pages: check your inbox, success, expired, already used and invalid. In my rules, expired always offers a new link, already used is treated as success when the address is verified, and no state reveals whether an account exists or keeps the token in the address bar.

The first page is the one the app shows right after signup. The other four states belong to the page the link opens. The table is my design rule set, one row per state.

StateWhat the user seesThe one action offeredWhat not to show
Check your inboxThe address the mail went to, a way to correct it, and a hint to look in spamResend, with a visible cooldownWhether that address already had an account
Success”Email confirmed”One button into the app, signed in only if the flow allows itThe token in the address bar
Expired”This link has expired”Send a new link to the same addressA dead end with no way forward
Already usedThe success screen, when the address is already verifiedContinue to the appAn error message
Invalid”This link isn’t valid”Request a new linkWhy it failed

Already used gets the success screen because, I believe, mail scanners and double clicks both cause it; Supabase’s docs note that certain email providers “may have spam detection or other security features that prefetch URL links from incoming emails”, which uses up a link before the person clicks. When the mail never arrives at all, the causes differ by builder, and they are set out under why nobody is getting the confirmation email.

What the landing page must not do, as my rule: reveal whether an address has an account, load third-party scripts or analytics while the token is in the URL, or leave the token in the address bar after use. Redirect to a clean URL once the token is checked. The browser default for Referrer-Policy is strict-origin-when-cross-origin, which sends other sites your origin at most; I set no-referrer on this page anyway, and with it “sent requests do not include any referrer information”, per MDN’s Referrer-Policy reference.

Accessibility in two lines: give each state a real heading and move focus to the message. Announce the state to screen readers, so a confirmation is heard, not only seen.

I design it for a phone first, because I expect the mail to be opened on a phone while signup happened on a laptop. That means the success state has to make sense on a device that is not signed in. The endpoint behind these states, single use and scanner handling included, belongs to the send path; the resend control’s cooldown and the provider’s own sending caps are covered under email rate limit exceeded.

How to verify the result

A verification email is tested with my seven checks: three mailbox providers on web and phone, classic Outlook for Windows, images off and dark mode, the plain-text part, a ten-second stranger read, the four landing states by clicking twice and after expiry, and a DMARC pass in the received headers.

I run all seven, in this order, on every template change. Each is written so a correct template passes and a broken one fails, whether the message goes out through a provider’s template or your app’s own send path.

  1. 01 Send the real message from staging to a Gmail, an Outlook.com and an iCloud address, and open each on the web and on a phone. The provider must be allowed to mail outside addresses first. Pass: a readable message and a working button in all six views. Evidence: screenshots.
  2. 02 Open it in classic Outlook for Windows, or a rendering preview if no Windows machine is at hand. Pass: the button and the column are intact. Evidence: a screenshot.
  3. 03 Turn images off, then switch the client to dark mode. Pass: the message still reads and the button is visible both times. Evidence: two screenshots.
  4. 04 View the plain-text part in the raw source or a text-only client. Pass: the preheader line, reason, link, expiry, not-you line and sender are all there (the subject is a header both parts share). Evidence: the raw source, saved.
  5. 05 Read it as a stranger for ten seconds: who sent it, what to click, when it expires, what if it was not me. Then search the raw source for {{ and }} and any dummy text the template carried. Pass: no match. Fail: one unfilled placeholder. Evidence: the search result.
  6. 06 Click the link twice, then again after the expiry (in staging, where the expiry can be waited out or shortened), then with a broken token. Pass: the four landing states as designed, a clean URL after the redirect, and no third-party requests in the network tab while the token is in the URL. Evidence: the network log.
  7. 07 Open the received message headers and read the Authentication-Results line. Pass: dmarc=pass, which needs SPF or DKIM to pass for a domain aligned with the From address. Evidence: the header lines.

Check 1 fails on a correct template if the sender is still fenced in. AWS’s SES docs say new accounts start in a sandbox, unique per Region, where “You can only send mail to verified email addresses and domains, or to the Amazon SES mailbox simulator”, with “a maximum of 200 messages per 24-hour period”. Supabase’s built-in mailer has its own fence, set out under who Supabase’s built-in email service will mail.

For check 7, RFC 9989 says a DMARC pass “requires not only an SPF or DKIM pass verdict for the email message but also and more importantly that the domain associated with the SPF or DKIM pass be ‘aligned’” with the From domain. One aligned pass is enough: a line where only one of SPF or DKIM passes, for an aligned domain, is still a DMARC pass. The DNS work to set up SPF, DKIM and DMARC before the template matters is its own job, and it comes first. A perfect template from an unauthenticated domain can still land in spam, and inbox placement before launch is its own test, the subject of how to test email deliverability.

Keep the dated set together: the screenshots, the raw source of one received message, the header lines, and the provider’s delivery record for the test send.

Where the sprint fits

Two deliverables of the Production Hardening Sprint sit behind this message. Deliverable 11.1, sending-domain authentication: we verify and configure SPF, DKIM, and DMARC for the transactional sending domain; deliverable 11.1 is verified this way: inspect DNS and received message headers for the configured authentication results. Deliverable 11.2, transactional delivery tracking: we configure a dedicated transactional provider with delivery tracking for account and transaction messages; deliverable 11.2 is verified this way: trigger each critical message type and confirm provider records and expected recipient delivery. Hosting, paid tools, and API usage remain in your accounts. Both deliverables are listed in the published scope.

Common questions about verification messages

How to create email verification?

Email verification sends a single-use link or code to the address and marks the address verified when that link or code comes back to the app. The token, the endpoint and the expiry belong to the send path, the subject of how to send a verification email; what this page adds is the message itself and the page the link lands on.

What is a verification code for email?

A verification code for email is a short one-time number sent to an address to prove the person can read that inbox. It expires, works once, and no one from the company that sent it will ask for it. Its length and lifetime come from the app’s own flow.

Where do I find my email verification code?

Your email verification code is in the newest message from the app’s sending address, and sometimes that message lands in the spam folder or a promotions tab. If you asked for more than one code and the app replaces older codes, only the newest one works.

Where can I find free email templates?

Free email templates are on this page (the three blocks under the template heading), in an independent MIT-licensed responsive template on GitHub, and in the free HTML template galleries that email builders such as Tabular, Stripo and RGE Studio (formerly Beefree) publish. Check the license and test any of them in classic Outlook for Windows before you use it.