POST once.
We handle the rest.
A multi-tenant email API with idempotent sends, versioned templates, automatic retries and failover, and signed webhooks for delivery status.
curl -X POST "$BASE_URL/api/v1/notifications" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"channel": "email",
"to": [{"email": "jane@example.com"}],
"template_key": "order_confirmation",
"variables": {"order_id": "12345"}
}'
< 202 Accepted
{"data": {"id": "ntf_550e8400...", "status": "queued"}}
What happens after your POST
Accepted for delivery is not delivered. The final outcome arrives by webhook, or you can poll for it.
Your app
Sends the request
Validate
Checks the payload, renders any template
Queue
Persisted, status: queued
SMTP
Delivered via the tenant's transport, with failover
Webhook
Signed callback on sent or failed
Or poll
GET the notification for its status
Built for the parts that usually go wrong
Idempotent by design
Send the same Idempotency-Key twice and the original response replays. Network retries are safe by default.
Versioned templates
Publishing a template creates a new version rather than overwriting it. Preview any change with /render before it goes live.
Tenant isolation
One token, one tenant. No cross-tenant reads, ever.
Automatic failover
Five retries with backoff. Each attempt can move to the tenant's next configured transport.
Signed webhooks
HMAC-SHA256, at-least-once, retried on non-2xx. Dedupe on the event id.
Verifying webhooks takes five lines
Every callback carries an X-Signature header. Recompute the HMAC over the timestamp and raw body, compare in constant time, and reject anything older than five minutes.
[$tPart, $vPart] = explode(',', $request->header('X-Signature'));
$timestamp = substr($tPart, 2);
$signature = substr($vPart, 3);
$expected = hash_hmac('sha256', $timestamp.'.'.$request->getContent(), $secret);
abort_unless(hash_equals($expected, $signature), 401);
abort_if(abs(time() - (int) $timestamp) > 300, 401);
Ask the platform owner for a token, then send.
An API token and a verified sender address are all you need to get started.
Open console