Users can subscribe to webhook events for `delivered`, `bounced`, and `failed` — the API validates these event types and stores the webhook config — but the events are never actually triggered.
Where webhooks DO fire
- `queued` — from `handleSendEmail` and `handleSendTemplate` in `handlers.go`
- `opened` — from `handleTrackOpen` in `tracking.go`
- `clicked` — from `handleTrackClick` in `tracking.go`
Where they should fire but don't
The delivery engine (`internal/smtp/delivery/delivery.go`) handles all three outcomes — delivered (line ~477), bounced (line ~426), failed (line ~357) — but has no reference to the webhook system. It can't call `triggerWebhook()` because that lives on the API `Server` struct and the delivery engine doesn't have access to it.
Why it matters
Anyone integrating with the send API expects to get notified when their email actually lands or bounces. Right now they can only know it was queued, then have to poll `GET /api/v1/emails/{id}` to find out what happened. The webhook config UI gives a false sense of coverage.
Possible fix
Add a delivery event callback (`OnDelivered`, `OnBounced`, `OnFailed`) to the delivery engine. The API server registers handlers during init that call `triggerWebhook()`. This keeps the delivery engine decoupled from the API layer.
Users can subscribe to webhook events for `delivered`, `bounced`, and `failed` — the API validates these event types and stores the webhook config — but the events are never actually triggered.
Where webhooks DO fire
Where they should fire but don't
The delivery engine (`internal/smtp/delivery/delivery.go`) handles all three outcomes — delivered (line ~477), bounced (line ~426), failed (line ~357) — but has no reference to the webhook system. It can't call `triggerWebhook()` because that lives on the API `Server` struct and the delivery engine doesn't have access to it.
Why it matters
Anyone integrating with the send API expects to get notified when their email actually lands or bounces. Right now they can only know it was queued, then have to poll `GET /api/v1/emails/{id}` to find out what happened. The webhook config UI gives a false sense of coverage.
Possible fix
Add a delivery event callback (`OnDelivered`, `OnBounced`, `OnFailed`) to the delivery engine. The API server registers handlers during init that call `triggerWebhook()`. This keeps the delivery engine decoupled from the API layer.