Guide

Reliable email workflows for AI agents

Handle duplicate webhooks, uncertain send results and worker restarts. Use signature verification, durable event records and stable idempotency keys.

Your agent reads an email and sends a reply. Then the webhook is delivered again. Without a record of the work already done, the next agent run can send a second reply that sounds almost the same.

This failure has little to do with the quality of the model. The application needs durable state around receiving an event, deciding what to do and sending the result. Here is a practical structure for an Agent Loadout email worker.

Verify the notification before accepting it

Create a webhook endpoint in the dashboard and subscribe to the event types your worker needs. For new incoming mail, start with message.received. Store the endpoint’s signing secret in the receiving application’s secret configuration.

The TypeScript SDK verifies Agent Loadout’s signature against the raw request body and timestamp. Inside your HTTP handler, before parsing JSON:

Signature check inside your request handler
import { verifyWebhookSignature } from "@agent-loadout/sdk";

const rawBody = await request.text();
const valid = await verifyWebhookSignature(secret, {
  signature: request.headers.get("x-loadout-signature"),
  timestamp: request.headers.get("x-loadout-timestamp"),
}, rawBody);

if (!valid) {
  return new Response("Invalid signature", { status: 401 });
}

const event = JSON.parse(rawBody);

Here request is the incoming Request and secret is the configured endpoint secret. Do not parse and reserialize the body before verification because the signature covers its exact bytes. Validate the event fields before using them. Verification shows where the notification came from; it does not make the email body a trusted instruction.

Persist the event before returning success

Store the event’s id with a unique constraint. Persist the work item in the same transaction or use a durable queue whose deduplication behavior you understand. Return a successful HTTP status only after your application has accepted responsibility for the event.

If persistence fails, return an error so delivery can be retried. If the same event arrives again after a successful insert, return success without creating a second job. An in-memory set loses that protection when the process restarts.

Have the worker fetch the message by ID using a scoped agent token. Map the inbox to the intended agent through your own stored configuration. A notification carries enough information to locate work; the current mailbox state is the source for what the worker may read.

Give the business action its own identity

Deduplicating webhook events is not the same as deduplicating replies. A manual retry or a second kind of event may reach the same business operation. Store a job record for the action you intend to perform, such as the first approved response to a support message.

Keep the proposed reply and its approval state in that record. Reuse the approved text on retries instead of asking the model to write a new answer every time. A useful record includes the source message ID, approved revision, send key and returned outbound message ID.

Reuse the same send key after a timeout

Agent Loadout accepts idempotency_key on sends and replies. Use one key for one intended action:

Example reply body
{
  "text": "The approved reply text.",
  "idempotency_key": "support-reply-msg_example-revision-1"
}

Post that body to /api/v1/messages/:id/reply. If the connection drops before you receive the response, retry the same request with the same key. Generating a fresh key turns the retry into another possible send. Keep the original payload with the key so its meaning does not change between attempts.

A successful API response means the message was accepted into the sending workflow. It does not prove delivery to the recipient’s inbox. Follow its status and the delivery or failure events separately. Avoid automatically issuing a new send when the outcome of the first attempt is uncertain.

Keep recovery visible

Record whether a job is waiting for review, ready to send, accepted or failed. Store error codes and IDs without copying full message bodies or credentials into routine logs. An operator should be able to find the original message and understand which step stopped.

Agent Loadout’s webhook history supports inspection and replay. A replay is another reason your receiver needs deduplication. If you use the events API instead, persist the returned cursor after processing the page and keep it after an empty page. Start from a saved cursor after a restart.

Test a duplicate notification, a failed database write, a worker restart and a timeout immediately after a send. The events reference and message reference describe the API contracts. Your application supplies the durable job and approval records around them.