Quick Answer

Email verification codes (OTP) confirm a user controls an email address during signup, account recovery, or sensitive actions. Send via a transactional email service (Postmark, SendGrid, AWS SES, Resend), not your marketing ESP. Codes should be 6 digits, expire in 5-15 minutes, rate-limit per address, and arrive within 30 seconds. Deliverability is critical — verification codes that arrive late or land in spam break onboarding.

Email Verification Codes: Implementation and Deliverability

By Braedon·Mailflow Authority·Email Infrastructure·Updated 2026-05-16

Email verification codes are part of the transactional email stack — the messages that have to arrive, fast, every time. When they fail, users can't sign up, can't recover passwords, can't complete sensitive actions. The deliverability requirements are different from marketing email and the infrastructure choices matter.

This guide covers implementing email verification code (OTP) flows that work reliably, with the infrastructure decisions that determine whether the codes actually arrive.

When you need verification codes

Common use cases for email verification codes:

Use caseCadenceSensitivity
Signup confirmationOne per signupMedium
Password resetTriggered by user requestHigh
Login 2FA via emailPer login (for some auth flows)High
Sensitive action confirmation (delete account, transfer funds)Per actionVery high
Email change confirmationPer email changeHigh

Verification codes are typically more time-sensitive than verification links — users expect them within seconds, paste them within minutes, and abandon if they don't arrive.

Code generation best practices

The standard pattern:

ElementRecommendation
Length6 digits (balance of usability and security)
Character setNumeric only (typed easily, less error-prone)
Expiration5-15 minutes
StorageHashed (not plaintext) with expiration timestamp
Single-useInvalidate after successful use
Rate limitingMax 3-5 codes per address per hour
Re-send policyAllow re-send after 30-60 seconds, max 3 attempts

In code (pseudo):

import random
import hashlib
from datetime import datetime, timedelta

def generate_verification_code():
    return ''.join(random.choices('0123456789', k=6))

def send_verification_code(email):
    # Generate code
    code = generate_verification_code()
    
    # Hash for storage (don't store plaintext)
    code_hash = hashlib.sha256(code.encode()).hexdigest()
    
    # Store with expiration
    db.store_code(
        email=email,
        code_hash=code_hash,
        expires_at=datetime.utcnow() + timedelta(minutes=10)
    )
    
    # Send via transactional ESP
    transactional_email.send(
        to=email,
        subject=f"Your verification code: {code}",
        body=verification_template.render(code=code)
    )

def verify_code(email, submitted_code):
    code_hash = hashlib.sha256(submitted_code.encode()).hexdigest()
    record = db.get_active_code(email=email)
    
    if record and record.code_hash == code_hash and record.expires_at > datetime.utcnow():
        db.mark_used(record)
        return True
    return False

Infrastructure for verification codes

Verification codes are transactional email. They need different infrastructure than marketing email:

Use a transactional ESP, not your marketing platform

Use thisDon't use this
PostmarkMarketing platforms (Mailchimp, HubSpot, etc.)
SendGridCold outreach tools (Smartlead, Instantly)
AWS SESPersonal Gmail accounts
ResendYour corporate mail server
Mailgun
Brevo (formerly Sendinblue)

Transactional ESPs are optimized for fast, reliable, single-recipient sends. Marketing ESPs prioritize bulk send efficiency, which adds latency unacceptable for verification flows.

For ESP selection see Postmark vs SendGrid vs other transactional services.

Use a dedicated transactional subdomain

Send from a subdomain reserved for transactional email:

  • auth.yourcompany.com or t.yourcompany.com (not your marketing subdomain)
  • Configured with SPF, DKIM, DMARC properly aligned
  • Isolated reputation from marketing sends

See sender reputation: domain vs IP for why subdomain isolation matters.

Authenticate fully

For verification codes to consistently reach the inbox:

  • SPF aligned with sending domain
  • DKIM signing on the sending domain
  • DMARC policy (at least p=quarantine) on the parent domain

See the DMARC setup guide for configuration. Without proper authentication, codes will increasingly land in spam, especially after Gmail/Yahoo's 2024 bulk sender rules.

Email content for verification codes

Best practices for the actual verification email:

Subject line

Clear and specific:

  • "Your verification code: 482931" — code in subject for fast scanning
  • "Your code for [App]: 482931" — branded variant
  • "[App] verification code" — minimal, code only in body

Avoid:

  • Generic subjects ("Action required") — pattern-matches as phishing
  • Marketing language ("Welcome to [App]!") — triggers Promotions filtering
  • All caps or excessive punctuation — spam signals

Body content

Keep it minimal:

Your verification code is:

482931

This code expires in 10 minutes.

If you didn't request this, you can ignore this email.

— [App] team

What to include:

  • The code, displayed prominently
  • Expiration time
  • Brief security note ("if you didn't request this...")
  • Single sender identifier

What to exclude:

  • Marketing links
  • Social media buttons
  • Multiple CTAs
  • Image-heavy templates
  • Long policy/legal footers in the body (link instead)

Heavy templated designs increase Promotions placement and slow rendering on mobile. Minimal designs reach inbox faster.

Delivery speed targets

For verification code delivery:

Time to deliveryUser experience
<10 secondsExcellent — feels instant
10-30 secondsGood — within expected range
30-60 secondsAcceptable — some user concern
60-120 secondsPoor — users start re-sending
>120 secondsFailed — users abandon

Achieving sub-30-second delivery requires:

  • Transactional ESP with established reputation
  • Dedicated transactional subdomain
  • Proper authentication
  • No queue delays in your application
  • Recipient's mail provider not throttling

Common deliverability problems

Codes landing in spam

Causes:

  • Sending from marketing infrastructure
  • Missing or misconfigured authentication
  • Generic subject that pattern-matches phishing
  • Recipient mail provider filtering all-numeric subjects aggressively
  • Sender domain has poor reputation

Fixes:

  • Move to transactional ESP and dedicated subdomain
  • Verify SPF/DKIM/DMARC configuration
  • Use branded subject ("[App] code") instead of generic
  • Establish reputation through consistent verification volume

Codes taking too long

Causes:

  • Marketing ESP with queue delays
  • Authentication failures causing greylist delays
  • Recipient mail provider throttling unknown sender
  • Application queue delays before send

Fixes:

  • Use transactional ESP optimized for fast delivery
  • Fix authentication
  • Build sender reputation over time
  • Audit application code for delays before send

Codes not arriving at all

Causes:

  • Hard bounces (typoed address, dead mailbox)
  • Recipient mail provider blocking the sender
  • Spam filter rejecting silently
  • Application bug not actually sending

Fixes:

  • Verify address at signup before sending verification
  • Monitor delivery rate per provider
  • Use ESP delivery webhooks to confirm sends
  • Add comprehensive logging

Practitioner note: I worked with a SaaS client whose verification codes were averaging 90+ seconds to deliver because they were sent via their marketing platform (Klaviyo). Signups were dropping at the verification step. Moving verification sends to Postmark on a dedicated transactional subdomain brought delivery to under 8 seconds and signup completion rate jumped 14%. Same email content, much better infrastructure.

Rate limiting and abuse prevention

Verification code endpoints need protection:

  • Rate limit per email address: 3-5 codes per hour max
  • Rate limit per IP: 10-20 codes per hour max
  • Exponential backoff on re-send requests
  • CAPTCHA after 3 failed verification attempts
  • Lock out address after 10 failed verification attempts in 1 hour

Without these limits, verification endpoints become attack vectors for spam, harassment, and account enumeration.

Verification code vs. verification link

The two patterns compared:

AspectVerification codeVerification link
User experience on mobileBetter (paste code, stay in app)Worse (open mail, click link, switch apps)
Security for sensitive actionsBetter (2FA-style)Equivalent if link is single-use
Email content sizeSmaller (code only)Larger (link + context)
DeliverabilitySimilarSimilar
Code expirationStandard 5-15 minStandard 24 hours typical
Resilience to email forwardingWorse (recipient must access mailbox)Better (link works from anywhere)
Implementation complexitySlightly more (verification endpoint)Slightly less (just URL)

For most modern signup flows, codes are preferred. For password reset, links are still common.

Compliance considerations

Verification code emails are transactional, not marketing:

  • CAN-SPAM (US) treats transactional differently from marketing
  • GDPR allows transactional email under "necessary for service" basis
  • Don't include marketing content in verification emails (changes them to mixed/marketing)
  • Provide unsubscribe link only if mixed-purpose (better to keep purely transactional)

Mixed-purpose emails (verification + marketing content) lose the transactional protections and add deliverability risk. Keep verification emails pure.

If you need help building reliable verification code flows with proper infrastructure, book a consultation. I work with SaaS teams on transactional email infrastructure and authentication setup.

Sources


v1.0 · May 2026

Frequently Asked Questions

What is an email verification code?

An email verification code (often called OTP — one-time password) is a short numeric code sent to a user's email address to confirm they control it. Used in signup confirmation, password recovery, login two-factor authentication, and sensitive action confirmation. Typically 4-8 digits, expires in 5-15 minutes, single-use. The alternative is verification via clickable link.

How does email verification with code work?

User enters email address; your system generates a random code (usually 6 digits), stores it with an expiration timestamp, and sends it via transactional email. User receives the email, enters the code into your interface. Your system checks the entered code against the stored value and timestamp. Match within expiration window = verified; otherwise reject and prompt to resend.

Verify email code vs. verify email link — which is better?

Codes work better on mobile (no need to switch apps and back), better when the verification is a step in a flow you don't want to leave, and better for sensitive actions (login 2FA). Links work better when you don't need to keep the user in the original flow, for password reset, and when the action is one-time. Most modern signup flows use codes for the first verification.

How fast should email verification codes arrive?

Target: under 30 seconds, ideally under 10 seconds. Anything over 60 seconds breaks the user experience — users assume it's not coming and either retry or abandon. Achieving fast delivery requires proper sending infrastructure (transactional ESP, not marketing platform), authenticated sending, and dedicated transactional subdomain.

Why are my email verification codes going to spam?

Common causes: sending from marketing infrastructure (Promotions filtering), missing SPF/DKIM/DMARC alignment, generic subject line matching marketing patterns, the recipient hasn't whitelisted your domain, sending from a recently-warmed sender without established reputation, or content that triggers filters. Fix: send from authenticated transactional subdomain via dedicated transactional ESP, with clear subject line and minimal content.

Want this handled for you?

Free 30-minute strategy call. Walk away with a plan either way.