Review an agent email draft before sending the exact message
Bind approval to recipients, content and attachments, then preserve one send identity through retries.
Persist a send identity before submission and separate definitive failures from uncertain provider outcomes.
An HTTP timeout does not tell you whether an email provider accepted a message. The server may have completed the action while the response was lost, or it may have stopped before contacting the provider. Retrying with a new identity can create a second message in the first case. Build your application around one durable attempt for one approved communication. Save its sender inbox, recipients, content revision and operation key before the network request. A process restart should restore that same attempt rather than generate a fresh random identity.
For POST /v0/emails/inboxes/:inbox_id/emails/send, provide an Idempotency-Key header. MCP sends, replies, reply-all, forwards and draft sends require an explicit idempotency_key argument. For resource creation such as drafts and inboxes, client_id serves a separate creation-deduplication purpose. Registration has its own stronger secret rules and must not reuse a mail-send key. Keep these identities in typed records so a general retry helper does not accidentally attach a registration recovery secret to ordinary messages or assume every create endpoint uses the same header.
Validation errors and permission denials require fixing the request or obtaining authority; they are not transient delivery problems. mail_rejected means the provider rejected the request before accepting delivery and the spending reservation was released. Unknown outcomes must remain unresolved until operation evidence or provider reconciliation establishes what happened. A recorded accepted message can recover local state without repeating the external action. Retain pending reservations and the original key where the system reports uncertainty instead of clearing state merely to make another request pass.
Store the returned message ID and known state, then show the human what is actually established. email.sent means provider acceptance, not that a recipient read the email. If no conclusive result exists, say the outcome is uncertain and stop issuing new sends for that action. Your next implementation step is a local fault-injection test that loses the response after an accepted send and restarts the worker. Assert that the same attempt is recovered, no second provider call occurs and the user-facing status never overstates the available evidence.
Record authorized_at, attempt identity, payload digest and observed outcome in your own application state. These fields answer different questions from a message label: what was approved, which execution it authorized and what evidence returned. Do not delete the pending record after a timeout just to unclog the queue; that removes the information needed for duplicate-safe recovery.
Reviewed against checked-in Lupin API handlers and documentation. Example requests were not executed against live providers.
Prepared with AI assistance by Lupin editorial. How we research and maintain these guides.