Files
mailflare/worker-utils.ts
Oday BakkourandClaude Opus 5 42cea463e0 feat(webhooks): management UI and delivery retry visibility
Closes the "webhook management UI and delivery retry visibility" roadmap item.
Delivery previously recorded a bare pending/delivered/failed with attempts fixed
at 1, and there was no way to see or retry anything.

Every attempt now records its HTTP status, response or error snippet, duration,
attempt count, and next retry time. Failures retry automatically with exponential
backoff (1m, 2m, 4m, 8m, capped at an hour) up to the endpoint's max_attempts,
then settle as "exhausted". Retries ride the existing OUTBOUND_QUEUE with a
delaySeconds rather than introducing a third queue binding, so nothing new has to
be provisioned; isWebhookRetryMessage discriminates them from outbound mail and
env.d.ts widens the queue's type to the union accordingly.

The admin page gains full management: add/enable/disable/delete, event selection,
per-endpoint delivery stats, a test delivery button, and a deliveries table with
manual retry. Delivery payloads now also carry X-Email-Platform-Delivery and
-Attempt headers alongside the existing HMAC signature.

Also returns 401 instead of 500 on unauthenticated requests to these routes, via
a new requireSessionUser helper; requireUser throws a bare Error, which Next
surfaces as a 500.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGeWDVSkXtVEmDAN2AtB9h
2026-08-30 10:18:30 +03:00

22 lines
662 B
TypeScript

import type { InboundQueueMessage } from "./src/lib/email/inbound";
import type { WebhookRetryMessage } from "./src/lib/email/webhooks";
export function isInboundQueueMessage(payload: unknown): payload is InboundQueueMessage {
return (
typeof payload === "object" &&
payload !== null &&
"rawR2Key" in payload &&
"from" in payload &&
"to" in payload
);
}
export function isWebhookRetryMessage(payload: unknown): payload is WebhookRetryMessage {
return (
typeof payload === "object" &&
payload !== null &&
(payload as { kind?: unknown }).kind === "webhook.retry" &&
typeof (payload as { deliveryId?: unknown }).deliveryId === "string"
);
}