Webhooks for captured email: a signed HTTP callback, or a message in Slack

Every captured email can now fire a webhook — a signed HTTP POST or a Slack message — scoped to one project or the whole organization. The payload, the signature, the retry schedule, the delivery log, and the SSRF guard that runs at delivery time.

The Mailclutch team2 min read

Every message that lands in a project can now fire a webhook. Two kinds: an HTTP POST to a URL of yours, signed, or a formatted message posted to a Slack incoming webhook. A webhook is attached either to one project or to the whole organization, in which case it fires for every project in it.

The payload

An email.received event carries the project — its id and name — and the message: id, subject, sender name and address, an excerpt, and the time it was received as an ISO-8601 timestamp.

Deliberately not the body. A webhook is a notification, not a transport: it tells you which message arrived so you can fetch what you need through the API, and a payload that carried the full message would mean the entire content of your test mail travelling to a third endpoint on every send. The event names follow the same domain.action shape as the activity journal, so one vocabulary covers both.

The signature

If the webhook has a secret, the delivery carries an X-MailClutch-Signature header: HMAC-SHA256 over the exact request body, keyed with the secret. The secret is encrypted at rest.

Verify it against the raw bytes you received, before parsing. Re-serializing the JSON and hashing that will fail, correctly — key order and escaping are not preserved by a round trip, and that is the whole reason a signature is computed over bytes rather than over meaning. Compare with a constant-time comparison, not ==.

Retries, and what a delivery log is for

Each delivery gets fifteen seconds to answer. A non-2xx response or a timeout is retried up to three attempts, backing off one minute, then five, then fifteen.

Every attempt is recorded: whether it succeeded, the HTTP status, how long it took in milliseconds, and the first 400 characters of the response body when it failed. That last field is the one that saves the afternoon — "it is not firing" is almost always "it fired, and your endpoint returned a 500 with a stack trace in it", and the delivery log is where you read the stack trace without adding logging on your side first.

The SSRF guard

A webhook URL is a URL a user gives us and our servers then fetch, which is the textbook setup for server-side request forgery. Deliveries to private and reserved address ranges are refused.

The check runs at delivery time, not only when the webhook is saved. A hostname that resolved to a public address when it was added can resolve to a private one later — that is a rebinding attack, not a hypothetical — so the address is re-validated on every attempt. A refused delivery is written to the log with its reason rather than silently dropped, so you can see it happened.

Setting one up

Project or organization settings, webhooks. Paste a URL, pick HTTP or Slack, set a secret if it is HTTP, and send yourself a test message from staging as usual — see the quickstart if you have not pointed anything at Mailclutch yet, and webhooks in the docs for the payload reference.

Related articles

Keep reading

All articles

Read about it here, check it on your staging.

See it on your own mail

Point your staging SMTP at Mailclutch and every message comes back with its headers, its spam score and its authentication results — before a customer sees it.

Free plan · no credit card · running in under a minute