Content Pipeline

Newsletter Double Opt-In Without a Database Token Table

2 min read

Double opt-in is non-negotiable for email lists. But most implementations store a token in a database table, then match it on confirmation. There’s a simpler way.

The HMAC approach

Instead of storing tokens, sign the email address with a secret key:

token = HMAC-SHA256(secret, email + "|" + timestamp)
confirm_url = /api/confirm?email=user@example.com&t=1715500800&sig=abc123

On confirmation:

  1. Recompute HMAC-SHA256(secret, email + "|" + timestamp)
  2. Compare with sig — if they match, the token is valid
  3. Check that timestamp is within 24 hours
  4. Flip subscriber status to active

No database reads for verification. No token cleanup cron. No leaked tokens table.

Implementation in Cloudflare Workers

const encoder = new TextEncoder();

async function sign(email: string, timestamp: number, secret: string): Promise<string> {
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const data = encoder.encode(`${email}|${timestamp}`);
  const sig = await crypto.subtle.sign("HMAC", key, data);
  return btoa(String.fromCharCode(...new Uint8Array(sig)));
}

Cloudflare Workers have the Web Crypto API built in. No dependencies needed.

Unsubscribe: RFC 8058 one-click

The List-Unsubscribe header with List-Unsubscribe-Post: List-Unsubscribe=One-Click tells email clients to send a POST on behalf of the user. No clicking, no confirming.

List-Unsubscribe: <https://yoursite.com/api/unsubscribe?email=...&sig=...>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

Same HMAC verification. Same stateless approach. One POST, one database update, done.

Security considerations

  • Secret rotation: Store the signing key in Workers Secrets. Rotate by accepting both old and new keys for 24 hours.
  • Rate limiting: Use KV counters to limit subscribe/confirm attempts per IP.
  • Timing attacks: Use crypto.subtle.timingSafeEqual for signature comparison — or just compare the full strings (btoa output is constant-length for SHA-256).

Why not just use a token table?

Token tables work. But they add:

  • A migration
  • A cleanup cron job
  • A database read on every confirmation
  • A potential data leak if the table is exposed

HMAC tokens add zero database operations for verification. The secret lives in Workers Secrets. The math is simple.


This pattern works for any verification flow: email confirmation, password reset, magic links. Stateless, secure, and free.

Ready to scale your content?

Book a free strategy session and get a personalized cross-platform distribution plan.

Book a Call