Webhooks
Told when it happens, not when you ask.

Register an https endpoint and get a signed POST when mail arrives, goes out, bounces or is opened.

In short

What is an email webhook?

A webhook is an HTTP request a service sends to your URL when something happens, so your code reacts instead of polling. For email, that means mail arriving, leaving, bouncing or being opened.

20

events to subscribe to

10

endpoints per mailbox

5

attempts per event at most

100

failed events in a row disable an endpoint

How it works

Every way of sending, one event

The composer, a send scheduled for Tuesday and the API all raise email.sent. Scheduling raises email.scheduled first.

Signed, and yours to check

Every POST carries an HMAC-SHA-256 of the timestamp and the raw body. Check the bytes as they arrived and refuse anything over 300 seconds old.

Retried only when it might work

Timeouts, 408, 425, 429 and 5xx are retried after 1, 5 and 25 minutes, then 2 hours. Any other 4xx is final, and 410 switches the endpoint off.

Twenty events in three families

Choosing none subscribes to every message event except email.replied.

email.receivedArrivedemail.repliedReply arrivedemail.sentAccepted for sendingemail.deliveredReceiver acceptedemail.delivery_delayedDelayed, retryingemail.failedCould not be sentemail.bouncedCame backemail.complainedReported as spamemail.suppressedSkipped, address suppressedemail.scheduledSet for lateremail.queuedUndo window runningemail.cancelledTaken backemail.openedOpenedemail.clickedLink followedemail.downloadedFile fetcheddomain.verifiedNow receivingdomain.sending_changedSending verdict moveddomain.deletedRemovedsuppression.addedAddress addedsuppression.removedAddress allowed again

Verify a delivery

The SDK checks the HMAC in constant time and refuses deliveries over five minutes old.

Pass the raw body, not parsed JSON.
import { verifyWebhookSignature } from '@openemail/sdk' export default async (request: Request) => {  const event = await verifyWebhookSignature({    payload: await request.text(),    headers: request.headers,    secret: process.env.OPENEMAIL_WEBHOOK_SECRET!  })   console.log(event.type, event.data)   return new Response(null, { status: 204 })}

What you get

In the product today

Five seconds, in parallel

Endpoints are called at once, so ten still take five seconds.

Public https only

Private and loopback hosts are refused, and a redirect is never followed.

Every attempt logged

Each one shows what your server answered and how long it took.

Good practice

Getting the most out of it

  1. 01

    Answer, then work

    Return a 2xx within five seconds and do slow work afterwards.

  2. 02

    Drop repeated ids

    Store each X-OpenEmail-Delivery id you handle and skip repeats.

  3. 03

    Rotate with both secrets

    Rotation replaces the secret at once, so verify against old and new until your deploy lands.

Questions

Asked often

Keep going

Works well with

REST API

A documented HTTP API with issuable, scopable, revocable keys.

Typed SDKs

A TypeScript client first, then the rest.

See when mail you sent was opened

Whether a message you sent has been opened, when, how many times and which of its links were followed. On unless you turn it off, and plain about the readings it cannot get.

Start

Your domain,
your mail.

Point a domain at OpenEmail and read it in a mailbox built around it. The free plan covers one domain.

Your inbox,
on your own terms.

Email infrastructure for businesses, AI, agents and personal email. Built for scale, privacy and control. Everything email should have had from day one.

OpenEmail

Email infrastructure for businesses, AI, agents and personal email. Built for scale, privacy and control. Everything email should have had from day one.

© 2026 OpenEmail. All rights reserved.