# Email verification template

URL: https://samva.dev/templates/email-verification

Confirm a new address with one button, a fallback code and the request details behind it. Edit it in the AI email editor.

## Guidance

- Show the address being confirmed so the recipient can spot a typo, and pass the request details as a list of label and value so they can tell their own action from someone else's.
- State the validity window in the expiry notice. The template never reads the clock; your application must keep that sentence aligned with the token it issued. The code is a string, so keep leading zeroes.
- Set the previous address only when the new one replaces it. The default fixture leaves it out and the email confirms a new address; long-content includes it and the email shows what it replaces.
- It is written for Plinth, a fictional platform for deploying web apps. Replace its mark and the envelope pictogram with your own, and restyle through the theme tokens in starter.css. Edit the verification email in the AI email editor and keep one-time passcode, password reset and welcome as separate sends.

## Variables

| Variable | Meaning |
| --- | --- |
| email | The exact address being verified, for example maya@halyard.example. Bind the verification token to this address in your application. |
| expiryNotice | For example: “The link and the code work for 30 minutes after this email was sent.” Use this wording only if both credentials share that lifetime. |
| previousEmail | Optional previous address when confirming an address change. Omit it for a new signup; do not expose the previous address unless your flow requires it. |
| requestDetails | A list of { label, value } objects, for example { "label": "Device", "value": "Safari 26 on macOS 26.1" }. Omit sensitive or unverified request context. |
| supportEmail | A monitored help address, for example support@plinth.example. |
| verificationCode | A string such as “482913”, validated by your own code-entry flow. Keep leading zeroes. This is required by the starter; remove the code block and update its input schema if your integration only issues links. |
| verifyUrl | The complete HTTPS verification URL from your auth system. Preview: https://plinth.example/account/verify/9f2c41b8. |

## Choose the Verification Variant

New signup: omit previousEmail and name the address the recipient is confirming. Send the welcome email after your application has accepted verification, rather than treating email delivery as proof of ownership.

Address change: populate previousEmail only for an authenticated change request. The email then shows the address it replaces and says account mail keeps going there until the new one is confirmed. Your application must bind the confirmation to the new address and decide when to replace the old one.

Link only: many auth callbacks provide a URL without a fallback code. Remove the code section and its required input in the editor for that flow. Never insert a decorative or randomly generated code that your application cannot validate.

## Keep Link and Code Behavior Consistent

The starter presents two ways to complete one verification. If you retain both, your backend must accept both for the same intended address, enforce their expiry, and prevent a successful confirmation from being replayed. The displayed code and URL are sample credentials only.

When verification expires, show a resend action in your application and explain which email to use after resending. If the recipient did not create an account or request an address change, they should be able to leave it unconfirmed and reach support without using the verification link.

Test the wrong code, expired credentials, repeated confirmation, and an address changed while an earlier link is still pending. A successful delivery event tells you about email transport; it does not mark an address verified.

## Wire the Verification Callback

The Better Auth integration exposes an emailVerification callback and template overrides. Use its user and URL as the source of recipient data and verifyUrl. The integration’s default template and this gallery starter are distinct; adapt the starter’s inputs to the credentials your callback actually receives.

Your authentication system owns token issuance, validation, expiry, and verified-account state. Keep that logic on the server, and set expiryNotice from the same lifetime you configure there.

## Implementation Guides

- [Send Authentication Emails With Better Auth](/docs/integrations/better-auth): Connect password reset, verification, and one-time codes to your authentication callbacks.
- [Write verification emails Gmail can copy](/blog/write-verification-emails-gmail-can-copy): How to put one code in a verification email, and what Gmail's Copy code button and the OTP-Token draft cover.
