Skip to content
Writing
NewWebhooksSecurityArchitectureTypeScriptNode

Production Email Webhook Architecture: Signatures, Retries & DLQs

Webhooks deliver delivery, bounce, and complaint events asynchronously. Here is the architecture needed to verify signatures, survive spikes, and prevent data loss with DLQs.

Tayyab MughalFounder & AI Chief3 min read

Why webhook receivers fail in production

Handling outbound email is straightforward: you call a REST API and get an ID. Handling inbound webhook events — deliveries, opens, clicks, bounces, and spam complaints — is where production email systems actually break.

Receivers fail for three predictable reasons: unverified endpoints accepting forged payloads, long-running database operations causing HTTP timeouts, and missing Dead-Letter Queues (DLQs) causing permanent data loss during downstream outages.

1. Verifying HMAC-SHA256 signatures in constant time

Never process an email webhook without cryptographically verifying its origin. An unauthenticated endpoint allows any attacker to forge bounce events and poison your suppression database.

SadaSend signs every delivery event with HMAC-SHA256 using your endpoint secret. Always verify the signature using constant-time comparison to prevent timing attacks.

TYPESCRIPT
import crypto from 'node:crypto';

export function verifyWebhookSignature(
  rawPayload: string,
  signatureHeader: string,
  secret: string
): boolean {
  // Header format: t=1726780000,v1=6a2b3c...
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('='))
  );
  const timestamp = parts['t'];
  const expectedSignature = parts['v1'];

  if (!timestamp || !expectedSignature) return false;

  // Reject timestamps older than 5 minutes to prevent replay attacks
  const ageSeconds = Math.floor(Date.now() / 1000) - Number(timestamp);
  if (Math.abs(ageSeconds) > 300) return false;

  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(`${timestamp}.${rawPayload}`);
  const computed = hmac.digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(computed, 'utf-8'),
    Buffer.from(expectedSignature, 'utf-8')
  );
}

2. Decouple ingestion from processing

The biggest mistake in webhook handlers is doing heavy work (database writes, third-party API calls, CRM updates) inside the HTTP request handler.

Email providers timeout requests after 5 to 10 seconds. If your database has a slow query spike, your receiver times out, the provider treats it as a failure, and initiates an avalanche of retries. Ingest immediately to Redis or BullMQ, return HTTP 200/202, and process asynchronously.

3. Surviving failures: Dead-Letter Queues (DLQs)

When a webhook fails 10 consecutive retries (due to a bug in your worker code or a database schema mismatch), the provider drops the event unless there is a Dead-Letter Queue.

SadaSend preserves failed deliveries in an inspectable Dead-Letter Queue, capturing the raw payload, HTTP status, and traceback, allowing you to fix your code and replay failed deliveries with one click.

Early Access

Building AI agents that send email?

Join the SadaSend early access waitlist to get scoped API keys, recipient allowlists, and Model Context Protocol (MCP) servers upon launch.

Use Case:
One email when it opens
Social Hashtags & Share
#EmailAPI#DeveloperTools#Webhooks#AppSec#CyberSecurity#AICompliance
Tayyab MughalFounder & AI Chief

Building SadaSend — transactional email with an MCP server that has a ceiling. Writes about deliverability, email infrastructure, and what happens when you hand an autonomous agent a sending credential.