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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions