Skip to content

Capture Resend delivery events via webhooks to measure OTP email delivery latency #185

Description

@aspiers

Problem

We have production complaints of OTP emails arriving after the 10-minute expiry (see #183, #184). ePDS's own logs can only ever show when we handed the message to Resend over SMTP — everything after acceptance (Resend internal queueing, recipient-side greylisting, full inboxes) is invisible to us, and Resend's REST API exposes only created_at and a last_event state per email, not per-event timestamps. So send→delivered latency cannot be measured retrospectively; it has to be captured as it happens via webhooks.

Proposal

Add a Resend webhook receiver and persist delivery events, so we can compute per-email delivery latency and alert on delays exceeding the OTP lifetime.

  • Subscribe to email.sent, email.delivered, email.delivery_delayed, email.bounced, email.failed at resend.com/webhooks.
  • Payloads carry type, created_at, and data (email_id, to, from, subject).
  • Persist events into a sqlite table keyed by email_id (auth-service DB or its own).
  • Metrics:
    • delivery latency = email.delivered.created_atemail.sent.created_at per email_id; track the fraction exceeding ~8–10 min (OTP expiresIn is 600 s, packages/auth-service/src/better-auth.ts:171)
    • count of email.delivery_delayed events (temporary delivery failure: greylisting, full inbox) — the prime suspect for late OTP arrivals
  • Join with the send-side log line (see Email sender: log duration, messageId and outcome for every OTP send #183) via recipient + Resend message_id ↔ nodemailer messageId, and with expired-OTP verification failures (Surface better-auth OTP verification failures (expired vs invalid) in server logs #184), to attribute each expiry complaint to delivery delay vs user behaviour.

Implementation notes

  • Webhook delivery is at-least-once with retries (5 s → 10 h) and events may arrive out of order: dedupe on the svix-id header, sort by created_at.
  • Verify the Svix signature on incoming requests.
  • Verify first: Resend docs confirm SMTP-sent emails appear in the emails table, but do not explicitly confirm webhook events fire for SMTP-submitted mail (our sends go via nodemailer SMTP, packages/auth-service/src/email/sender.ts). One test send through production SMTP credentials with a webhook registered settles this before building anything.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions