← Blog

Retry-safe email sending for AI agents

SentFromAI

To prevent duplicate emails from an AI agent, persist one identifier for each intended send, reuse it on every API retry, and keep the approved recipients and body unchanged. Combine the provider's documented idempotency mechanism with durable application state. A retry should repeat the same action, not ask the model to invent a new one.

SentFromAI documents client_id on new message sends: repeating that identifier returns the original message instead of sending a duplicate. That is a send-request contract, not a guarantee that every workflow step runs once or that a person receives and reads the email. SentFromAI sending guide

A timeout is an uncertain outcome

Consider a hypothetical support agent that sends a follow-up. The provider accepts the request, but the response never reaches the worker. The worker sees a timeout. It cannot tell whether the email was accepted or whether the request failed before reaching the provider.

Sending again with a new operation ID can create a second email. Recording the task as complete before making the API call creates a different risk: a crash can leave the task marked done even though no request was made.

AWS describes this ambiguity in its guide to making retries safe with idempotent APIs. Caller-provided request identifiers let a service recognize repeated attempts at one operation. For an email agent, the identifier must survive the worker that first tried to send.

Identify the action, not the attempt

A logical send is one approved communication with a specific sender, recipient set, and message. A network attempt is one effort to submit that communication. Several attempts may belong to the same logical send.

Generate the send identifier when the application commits to the action, then persist it before the first API call. You can generate a random identifier once or derive one from a stable business-action ID. Do not generate a new random value inside each retry.

For example, tenant-7-case-4821-followup-1 is an illustrative action key. Every retry of that follow-up uses the same key. A deliberately approved second follow-up gets a different action ID. Keep sensitive customer data out of identifiers that may appear in logs.

Avoid using only the email body, subject, or thread ID as your key. Two legitimate messages can have identical text, and one thread can contain many legitimate sends. AWS makes the same distinction between matching request parameters and matching caller intent. AWS idempotency guidance

Use SentFromAI's documented send field

The current send API is POST https://api.sentfrom.ai/v1/messages. Its JSON schema includes client_id alongside the required inbox_id, to, and subject fields. The following is an illustrative request body, checked against that schema. Replace the inbox placeholder and use a test recipient you control; it is not a recorded production send. Send API reference

{
  "inbox_id": "INBOX_ID",
  "to": ["recipient@example.com"],
  "subject": "Case 4821 follow-up",
  "text": "Here is the approved follow-up for this case.",
  "client_id": "tenant-7-case-4821-followup-1"
}

Send it from your backend with bearer authentication. Load the API key from your secret store, not from an email body or a browser bundle. On a retry, reuse the stored client_id and the same request body. Do not ask the model to regenerate the answer, pick different recipients, or change the subject while preserving the old key.

The public sending guide documents returning the original message for a repeated client_id. It does not specify key retention, key scope, or changed-payload and concurrent-request behavior. Verify those details before depending on indefinite replay or parallel submissions. Keep your own send ledger even when provider idempotency is available.

Keep a durable outbox outside the model

An outbox is an application record of approved communications waiting to be submitted. The model can propose a message; the application decides whether to create the send intent, whether approval is required, and whether a worker may claim it.

A useful record contains:

  • the tenant, inbox, workflow, and business-action identifiers;
  • the persisted provider idempotency key;
  • the exact recipients, subject, body, and attachment references;
  • the approval state and payload version;
  • the submission state, attempt count, and next retry time;
  • the returned provider message and thread identifiers;
  • the last error and any unresolved-outcome flag.

Use a database uniqueness constraint on your logical action key and an atomic claim or lease so two workers do not independently create or submit the same action. These are recommended application patterns, not features this article claims SentFromAI supplies.

A worker claim does not make your database transaction atomic with a remote email API. The provider might accept a request just before the worker crashes or loses its lease. Persisted request identity bridges that gap within the provider's contract; uncertain results still need reconciliation. A boolean called sent cannot represent all these states safely.

Freeze the approved payload for a send intent. If a person edits a draft before its first submission, update that unsent intent under your application policy. If an earlier attempt may already have been accepted, do not silently turn its retry into an edited message. Resolve the uncertain outcome first, then create a separate approved action if another email is necessary.

Retry failures that can actually recover

Idempotency does not mean every error should be retried. Distinguish a malformed request, a policy or plan limit, and an uncertain network result.

Result Application response
Timeout or connection loss Keep the outcome uncertain. Retry the unchanged send only within the verified idempotency contract, or reconcile and escalate.
400 malformed request Correct the request rather than repeat it unchanged.
401 authentication failure Surface the API-key problem. Do not retry unchanged.
402 plan-limit error Resolve the limit or billing issue before resuming.
422 no deliverable recipients Stop this send and inspect suppression or blocking. Do not loop around the recipient policy.
Successful response Store the returned identifiers and stop submission retries for that action.

The status meanings above come from SentFromAI's error guide. Timeout handling is an application recommendation, not a newly documented SentFromAI error code.

Use bounded retries, increasing delays, and a total deadline rather than an immediate endless loop. Review retry behavior across your queue, HTTP client, and agent framework so they do not all retry independently. When the deadline expires, preserve the key and payload for investigation; do not automatically create a fresh send intent to clear the error.

Separate submission from delivery

Your outbox should distinguish an API submission result from later email delivery events. A successful API response does not prove inbox placement or reading.

SentFromAI defines message.delivered as acceptance by the recipient's server. Its receiving guide lists separate bounce, complaint, and rejection events. Record these against the appropriate message instead of treating every later failure as permission to submit the original email again. Receiving events

A retry key also does not deduplicate the agent's reasoning or its other tools. If an inbound event is delivered twice, two workers can each create a different outbound action key and send two different messages. Deduplicate the incoming work and enforce the business rule that determines whether a response is still needed. The email-to-webhook guide covers durable inbound acceptance; the receiving-modes comparison explains when to use webhooks, WebSockets, or polling.

Do not assume every endpoint has the same contract

SentFromAI's new-message schema exposes client_id. The reviewed reply endpoint schema lists text, html, and reply_all, but no client_id request field. Do not invent that field or assume that a response named idempotent establishes caller-controlled retry support. For replies, preserve thread context and keep uncertain sends visible until you verify the supported recovery behavior. See email threading for AI agents.

Provider contracts differ. AgentMail currently documents an Idempotency-Key header for sends, replies, forwards, and draft sends, with organization scope and expiry 24 hours after completion. It documents a 409 conflict when the same key is used for a different request. Those are AgentMail's semantics, not values to copy into a SentFromAI implementation. AgentMail idempotency docs

Compare endpoint coverage, retention, key scope, changed-payload behavior, and concurrent-request behavior before switching providers. These implementation questions sit alongside the broader product tradeoffs in the SentFromAI and AgentMail comparison.

Test the failure paths before enabling autonomous sends

Use an inbox and recipient you control. The following are proposed integration tests, not reported test results:

  1. Repeat an identical supported send request with the same key. Check the returned message identity and count what reaches the recipient.
  2. Simulate losing the response after request submission. Restart the worker and confirm it loads the persisted key and payload rather than creating another action.
  3. Start two workers against one intent. Confirm the application's atomic claim prevents routine parallel submission, and separately test the provider's concurrent-request contract.
  4. Deliver the same inbound event twice. Confirm it does not create two outbound intents.
  5. Return an authentication, plan-limit, or recipient-policy error. Confirm the worker stops instead of retrying endlessly.
  6. Exceed the application's retry deadline. Confirm the outcome remains visible for reconciliation rather than being silently marked sent or replaced with a fresh key.

Never treat a mock's duplicate suppression as proof of the live provider's behavior. Test the documented API separately, and ask the provider about retention or endpoint guarantees that are not public.

Next step

Read the SentFromAI sending documentation, then build the persisted send-intent boundary before connecting a model to production recipients. SentFromAI email explains the inbox model, and current pricing lists the available allowances.

Sources

Documentation checked October 6, 2026. The request example is schema-checked, not a live-send benchmark.

Give your agents an inbox today

Start free with 5 inboxes and 5,000 emails. Scale to thousands when you're ready.