Mailgun events
Mailgun fires webhook events for every email’s full lifecycle. We verify, dedup, store, and use these to power the delivery watchdog + per-booking timeline.
Events received
| Event | Meaning | Terminal? |
|---|---|---|
accepted | Mailgun queued for delivery | No |
delivered | Recipient’s mail server accepted | Yes (success) |
failed | Send failed (severity=permanent or temporary) | Yes (failure) |
rejected | Mailgun pre-emptively dropped (suppression list, etc.) | Yes (failure) |
opened | Recipient opened the email (tracking pixel) | No |
clicked | Recipient clicked a tracked link | No |
unsubscribed | Recipient hit the unsubscribe link | No |
complained | Recipient marked as spam | Yes (failure) |
Webhook handler
/webhook/mailgun (src/routes/webhook-mailgun.ts):
- Parse JSON body
- Verify signature: HMAC-SHA256 of
signature.timestamp + signature.tokenkeyed byMAILGUN_WEBHOOK_SIGNING_KEY, hex compared withsignature.signature - Reject if timestamp skew > 15min (replay defense)
- Insert into
mailgun_events(dedup onevent_idunique index) - Sync terminal-state to
outbound_log:delivered→markOutboundDeliveredfailed/rejected→markOutboundFailedwith reason
- Alert on permanent bounce + spam complaint: SMS Bill (once per
event_id, no re-alert on retries)
Tagging
Outgoing sends include h:X-Mailgun-Variables with {booking_id, purpose}. Mailgun echoes them back on every event as
event-data.user-variables. We extract + store in the
mailgun_events.booking_id + purpose columns.
This is what makes per-booking delivery views work — every event can be joined back to the booking that triggered the send.
Purposes in use
heads_up,t2_reminder,time_commit,escalation_*— cleaner touchpointspre_arrival_t14,t5,t3,t1,t0_codes,t0_nudgeaddon_early_checkin_confirmed/declined,addon_late_checkout_*cohost_new_bookingbill_arrival_day,bill_departure_day,bill_dirty_at_3pmform_bridge_pre_checkin/checkin/checkoutcleaner_cancel_notify,late_checkout_cleaner_notifyeta_miss,no_cleaner_alert,ack_confirm,complete_confirmclean_issue,sms_forward,guest_checkout_eveota_airbnb_confirm,checkin_form_chasedaily_summary,t0_missing_body,addon_combined_deferral
Schema (mailgun_events)
event_id, event, recipient, message_id, domain, event_ts,reason, severity, booking_id, purpose, raw, received_atraw is the full event-data JSON for forensic queries.
Source
src/routes/webhook-mailgun.ts— receiversrc/lib/mailgun-signature.ts— HMAC helpersrc/mailgun/client.ts—sendEmailadds variables + records to outbound_log on sendmigrations/0022_mailgun_events.sql,0023_mailgun_event_tagging.sql- Tests:
test/routes/webhook-mailgun.test.ts(19 cases) +test/lib/mailgun-signature.test.ts(8 cases)