Features
Webhooks
Signed POSTs to your server the moment an email is delivered, bounces, is opened or clicked. The only way to learn what happened to a message you sent.
Delivery updates, pushed to you
Anything that happens to one of your messages arrives at a URL you own, within seconds. Accepted by the recipient’s mail server, bounced, opened, clicked: your systems learn the outcome as it happens.
For history rather than live events, your delivery logs are in the dashboard.
sequenceDiagram
accTitle: How a delivery event reaches your server
accDescr: Your application posts a message to mailkube and gets an id back. mailkube delivers the message to the recipient's mail server, which accepts it, and mailkube then posts a signed email.delivered event to your webhook endpoint, which answers 200 OK.
participant A as Your application
participant M as mailkube
participant R as Recipient mail server
participant W as Your webhook endpoint
A->>M: POST /emails
M-->>A: 200, id and message_id
M->>R: Deliver the message
R-->>M: 250 accepted
M->>W: POST email.delivered, signed
W-->>M: 200 OK
Note over M,W: A non-2xx is retried with backoff.<br/>X-Webhook-Id stays the same, so you can deduplicate.Setting one up
Endpoints are managed in the dashboard by organization owners, admins and members. An endpoint belongs to one active sending domain, fixed at creation, and subscribes to the events you pick.
Before the endpoint is saved, you have to prove you control the URL. A single GET arrives carrying a one-time token:
GET <your-endpoint>?hub.mode=subscribe&hub.challenge=<one-time-token>
Your server has 5 seconds to answer 200 with that token as the body. Echoing the challenge is the whole proof, with no shared verify token to configure. A wrong body, a non-200, a TLS error or a timeout means the endpoint is not created.
sequenceDiagram
accTitle: The verification handshake when you add a webhook endpoint
accDescr: From the dashboard you submit an endpoint URL and pick events. mailkube sends a one-time challenge token to that URL. Your endpoint echoes the token back with a 200 within five seconds, and mailkube then creates the endpoint and shows the signing secret once.
participant D as You, in the dashboard
participant M as mailkube
participant W as Your webhook endpoint
D->>M: Submit endpoint URL and pick events
M->>W: GET with hub.challenge=TOKEN
W-->>M: 200, body is TOKEN
M-->>D: Endpoint created, signing secret shown once
Note over M,W: The endpoint is not created unless the token comes back within 5 seconds.Each event goes to one endpoint per domain. Subscriptions cannot silently overlap and deliver the same event twice.
Events
Nine email events cover the life of a message: email.scheduled, email.sent, email.delivered, email.bounced, email.delivery_delayed, email.failed, email.suppressed, email.opened and email.clicked.
Two of them are worth reading closely. email.bounced fires only when the address is permanently dead, while anything temporary, including a message that ran out of retries, arrives as email.delivery_delayed. Treating the second as a hard bounce is how good addresses get suppressed by mistake.
email.suppressed fires when a recipient is filtered out of a send. That happens when they previously hard-bounced on your domain’s apex, or unsubscribed from the topic the send was attributed to. The send counts against your quota and the message is never transmitted. Clean the address out of your own list when you see this event.
Two lifecycle events sit alongside them: domain.status when a sending domain changes state, and webhook.status when an endpoint does.
Payload
Every delivery has the same envelope. The event name is type. Everything specific to it sits in data, including the tags you attached at send time.
{
"type": "email.delivered",
"created_at": "2026-06-30T12:00:00.000Z",
"data": {
"email_id": "56761188-7520-42d8-8898-ff6fc54ce618",
"from": "Acme <hello@acme.com>",
"to": ["alice@example.com"],
"subject": "Sending this example",
"domain": "acme.com",
"tags": [{ "name": "campaign", "value": "welcome_series" }],
"delivery": {
"recipient": "alice@example.com",
"timestamp": "2026-06-30T12:00:00.000Z"
}
}
}
Verifying a delivery
Three headers ride on every POST. X-Webhook-Sig is an HMAC-SHA256 signature over the delivery id, the timestamp and the raw body. X-Webhook-Ts is the timestamp of this attempt, fresh on every retry. X-Webhook-Id is the delivery id, stable across retries of the same event. Deduplicate on it.
Every SDK ships the check. Hand it the raw bytes and the request headers rather than implementing the signature yourself. It compares in constant time, rejects a delivery older than 300 seconds, and returns a typed event. The wire contract is documented for languages with no mailkube SDK.
The signing secret is shown once, when the endpoint is created, and can be rolled from the dashboard if it leaks. Rolling takes effect immediately and cannot be undone. Update the receiver first.
Endpoint health
A failed delivery is retried up to fifteen times, backing off from one minute to a cap of 48 hours. Separately, your acceptance rate on the first attempt is tracked over a rolling window.
An endpoint whose acceptance rate falls below the threshold is disabled automatically, and listed as disabled for low quality rather than by you. Anyone subscribed to webhook health alerts is emailed with the rate that triggered it. Fix the receiver, switch the endpoint back on, and measurement restarts from a clean slate.
Event payloads and endpoint setup
- The webhooks guide(opens in a new tab) has the payload for every event and a verified receiver in each language
- Endpoints, subscriptions and signing secrets live at app.mailkube.com