email webhooks, rest api, hmac, automation, integrations

How to Process New Emails with Webhooks

Relay does not emit webhooks itself. Learn how to build a polling adapter that delivers signed, retried, idempotent webhook events with replay protection and observable failure handling.

Webhooks are a convenient way to connect new email to workflows such as account verification, ticket creation, and agent orchestration. They push work to your application instead of making every consumer poll an inbox. Relay's REST API is the source of truth for retrieving mail; Relay does not currently emit webhooks. If your architecture needs push delivery, build a small adapter that polls Relay and emits webhooks under your control.

That distinction is useful: your adapter owns event identity, signing, retries, and delivery policy, while Relay owns mailbox access. A careful implementation can be simple without pretending that exactly-once delivery is possible.

Signed webhook from inbox to HTTPS endpoint with idempotent message fetch

Put an adapter between Relay and your consumers

Use four components: a poller, a durable event store, a delivery worker, and your webhook receiver. The poller reads messages through the Relay API. Before emitting an event, it stores a unique event ID, the inbox and message identifiers, the payload hash, and the delivery state. The worker then delivers pending events independently of polling.

Relay REST -> poller -> transactional event store -> delivery worker
                                                      |
                                                      v
                                           your signed webhook endpoint

Keep the Relay API key only in the poller or worker that needs it. Use a separate webhook signing secret for consumers. Bearer credentials authenticate your adapter to Relay; HMAC signatures authenticate your adapter to receivers. Never place process.env.RELAY_API_KEY in a payload, log, browser bundle, or webhook URL.

This separation also lets you replay a failed delivery without fetching the message again or creating a second logical event. The Relay documentation should remain your reference for current API response fields and limits.

Poll with durable cursors and explicit state

For each inbox, request GET https://www.relaymcp.net/api/v1/inboxes/:id/messages with a limit and, when available, your saved cursor. Authenticate with Authorization: Bearer <key>. Treat the returned page and cursor as untrusted input until you validate them against the API schema.

Persist the cursor only after the corresponding messages and outgoing events are committed. Otherwise, a crash can advance the cursor and permanently skip mail. If a transaction cannot cover both operations, make the event insert idempotent and rerun the page after recovery.

Use one database row per message identity, with a unique constraint on your normalized inbox-and-message key. Do not use the subject, sender, or arrival time as an identity: all can repeat. Poll different inboxes independently so one slow inbox does not block the rest.

Choose page sizes from observed throughput rather than maximizing every request. A smaller page limits how much work must be repeated after a crash; a larger page reduces request overhead. Add a bounded overlap or reconciliation read if your cursor semantics do not cover messages arriving during a poll, and rely on the unique message constraint to absorb duplicates.

State Purpose
Relay cursor Resume polling without rereading the full history
Event ID Give consumers a stable deduplication key
Delivery attempt Drive backoff and diagnostics
Payload hash Detect accidental mutation between attempts

Sign timestamps and prevent replay

Sign the exact raw request body, not a parsed and reserialized object. Include a timestamp and event ID in a signed header. A receiver can then verify both integrity and freshness before doing any work.

import { createHmac, timingSafeEqual } from 'node:crypto';

function sign(rawBody, eventId, secret, now = Date.now()) {
  const timestamp = Math.floor(now / 1000);
  const signed = `${timestamp}.${eventId}.${rawBody}`;
  const signature = createHmac('sha256', secret).update(signed).digest('hex');
  return { timestamp, signature: `v1=${signature}` };
}

function verify(rawBody, eventId, header, secret, tolerance = 300) {
  const age = Math.abs(Math.floor(Date.now() / 1000) - header.timestamp);
  if (age > tolerance) return false;
  const expected = Buffer.from(sign(rawBody, eventId, secret, header.timestamp * 1000).signature);
  const actual = Buffer.from(header.signature);
  return expected.length === actual.length && timingSafeEqual(expected, actual);
}

Receivers should store accepted event IDs for at least the retry window and reject duplicates. A timestamp restricts replay; it does not replace deduplication. Require TLS, rotate signing secrets with an overlap period, and include a key version when rotation is active. Avoid putting message content in signature logs.

Design retries, idempotency, and ordering together

Webhook delivery is at least once in practice. Timeouts are ambiguous: the receiver may have completed the work even though your worker saw no response. Retry with exponential backoff and jitter, cap the total attempts or elapsed time, and move exhausted events to a dead-letter state with an operator-controlled redrive path.

Make the receiver's side effect and event dedupe record part of one transaction whenever possible. Return a successful response only after that commit. A 409 caused by a unique event constraint can be treated as an already-applied delivery if your transaction semantics confirm it.

Do not promise global ordering. SMTP arrival, polling, retries, and parallel workers can reorder events. If a consumer needs per-inbox ordering, serialize delivery by inbox and include a recorded sequence number, but expect reduced throughput and head-of-line blocking. Often a better design is to make each event self-contained and let consumers fetch current state from Relay when necessary.

Monitor delivery as a production system

Track polling lag, oldest pending event age, retry rate, dead-letter count, response latency, and receiver failures by status class. Alert on sustained backlog growth rather than a single transient failure. Keep structured logs with event ID, inbox ID, attempt, and outcome, while redacting message bodies, addresses, authorization headers, and signatures.

Test the ambiguous cases: a crash after polling but before commit, a receiver timeout after commit, duplicate pages, clock skew, bad signatures, and a poison event that always fails. Run a synthetic inbox-to-receiver check in a controlled environment so monitoring covers the whole path, not only process uptime.

For implementation boundaries and endpoint details, see Relay's docs and keep your adapter's API contract versioned separately from Relay's.

Frequently asked questions

Does Relay send webhooks directly?

No. Relay exposes email through its REST API and MCP tools. The architecture in this guide uses your own adapter to poll the REST API and emit signed webhooks to your chosen endpoint.

Can HMAC signatures prevent duplicate processing?

No. Signatures establish authenticity and integrity, while timestamps limit replay windows. Your receiver still needs a durable event-ID deduplication mechanism because legitimate retries can deliver the same valid event more than once.

Should the webhook include the complete email?

Usually no. Send the minimum metadata your consumer needs, or an opaque reference that lets an authorized service retrieve the message. Smaller payloads reduce sensitive-data exposure, signature overhead, and accidental content logging.

Build push delivery without changing the source of truth

A polling adapter gives you webhooks while preserving Relay as the mailbox access layer. Durable cursors, idempotent event creation, signed raw payloads, bounded retries, and explicit replay handling provide a reliable foundation. Start with one inbox and one receiver, exercise failure recovery, then scale workers using measured backlog and latency. Explore Relay when you are ready to connect temporary inboxes to your own event architecture.

FROM THE COLLECTIONS

Continue with related guides

More practical reading on agent-friendly email, testing, privacy, and secure automation.