# Production Email Webhook Architecture: Signatures, Retries & DLQs

_By Tayyab Mughal, Founder & AI Chief · 19 September 2026 · 3 min read_

> How to build resilient webhook receivers for email events: HMAC-SHA256 signature verification, exponential backoff retries, idempotency, and Dead-Letter Queues (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.

## 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.

> Never return a 200 OK on an invalid signature or corrupted payload. Returning 200 tells the sender the event succeeded, preventing legitimate retries.

---

_Tags: Webhooks, Security, Architecture, TypeScript, Node_
