Skip to main content

Event Delivery Model

mailbot pushes webhook events to your endpoint when interesting things happen on an inbox: a message is sent, delivered, opened, clicked, bounced, or received. This page describes the contract: when events fire, in what order, and what your endpoint must handle.

Event types

EventWhen it fires
message.sentOutbound message accepted by the upstream MTA
message.deliveredRecipient mailbox accepted the message
message.openedTracking pixel loaded by the recipient
message.clickedTracking link followed by the recipient
message.bouncedRecipient mailbox rejected the message
message.receivedInbound message landed on one of your inboxes
message.parsedInbound message parsed and ready for downstream handlers

Each event payload includes the event_id, event_type, inbox_id, the relevant message_id and thread_id, and a timestamp. Full payload schemas are in the API reference.

At-least-once delivery

Webhook delivery is at-least-once. mailbot guarantees that every event is delivered to your endpoint at least once. It does not guarantee exactly-once.

This means your endpoint must be idempotent. If mailbot retries an event after a transient failure, your handler will see the same event_id again. Treat event_id as a deduplication key.

A simple idempotency check:

async function handleEvent(event) {
const seen = await db.events.findOne({ event_id: event.event_id });
if (seen) return; // already processed
await db.events.insert({ event_id: event.event_id, processed_at: new Date() });
await processEvent(event);
}

Retry policy

If your endpoint returns a non-2xx response, mailbot retries with exponential backoff:

  • attempt 1: immediately on event
  • attempt 2: after 30 seconds
  • attempt 3: after 2 minutes
  • attempt 4: after 10 minutes
  • attempt 5: after 1 hour
  • attempt 6: after 6 hours

After 6 failed attempts, the delivery is marked failed and stops retrying. The event is preserved and remains replayable indefinitely via event replay.

A 2xx response (200, 201, 202, 204) is treated as success and stops retries. Anything else, including 3xx redirects, is treated as failure.

Ordering guarantees

mailbot does not guarantee strict global ordering across events.

Within a single message lifecycle, events generally arrive in the natural sequence: sentdeliveredopenedclicked. But your handler must not depend on this order. A delayed network can reorder webhook deliveries even when events were emitted in order on the mailbot side.

Two safe patterns:

  1. Order by event timestamp, not arrival time — every event payload carries created_at. Reconstruct order from the timestamp, not the sequence in which your endpoint received them.
  2. Use idempotent state transitions — design your data model so applying events out of order still converges to the right state. For example, "set opened_at to the earliest known open" is order-independent.

Per-message vs per-inbox dispatch

Webhook destinations are configured at the inbox level. Events for messages on inbox A only fire to webhooks registered for inbox A. There is no global webhook fan-out.

If you want a single endpoint to receive events from many inboxes, register the same URL on each inbox. mailbot treats them as independent destinations, so retries and failures are scoped per inbox.

Signature verification

Every webhook delivery includes an X-mailbot-Signature header. Verify it before trusting the payload. See Webhook reliability for the verification recipe.