Skip to main content

Webhooks

ProgrammableInbox receives inbound mail via a webhook from Resend (email.received events), verified with an HMAC signature and a 5-minute replay window.

Sync vs. async ingestion

By default, ingestion happens inline on the webhook request: the message is parsed, threaded, and stored before the response is sent. Setting ASYNC_WEBHOOK_PROCESSING_ENABLED=true instead queues the job to a Redis/BullMQ worker — useful for high-volume inboxes where inline processing would make the webhook response too slow. Both paths run the same threading, search-indexing, and automation logic; total attempts on the async path are capped by WEBHOOK_QUEUE_MAX_ATTEMPTS (default 4).

Threading

New messages are matched to an existing thread first by In-Reply-To / References headers against a prior message's Message-ID, then by a normalized subject match within the same inbox. This subject fallback matters because mail providers don't always preserve the Message-ID a recipient's client actually sees.

Duplicates

A message that arrives twice (the same external id for the same inbox) is silently skipped rather than stored twice.