The Outbox Pattern
import { Aside, Code } from ‘@astrojs/starlight/components’;
Your agent doesn’t send email directly. Instead, it writes files to an outbox directory, and the platform picks them up and sends them.
Why an outbox?
Section titled “Why an outbox?”Reliability
Section titled “Reliability”If the email API fails, the file stays in the outbox. The platform retries.
Auditability
Section titled “Auditability”Every outgoing email is a file you can inspect. Nothing hidden.
Simplicity
Section titled “Simplicity”The agent doesn’t need email credentials or API access. Just write a file.
How it works
Section titled “How it works”Agent writes JSON → /data/outbox/email/ ↓ Router polls outbox directory ↓ Picks up pending files ↓ Sends via Resend API ↓ Moves to /data/outbox/sent/ or /data/outbox/failed/Email JSON format
Section titled “Email JSON format”To send an email, your agent creates a file like this:
{ "subject": "Hello from your agent", "body": "This is the email body.\n\nIt supports multiple paragraphs.", "status": "pending"}Required fields
Section titled “Required fields”| Field | Type | Description |
|---|---|---|
to | string[] | Array of recipient emails |
subject | string | Email subject line |
body | string | Plain text email body |
status | string | Must be "pending" |
Optional fields
Section titled “Optional fields”| Field | Type | Description |
|---|---|---|
in_reply_to | string | Message-ID for threading |
references | string | Email thread chain |
cc | string[] | CC recipients |
bcc | string[] | BCC recipients |
Threading
Section titled “Threading”To reply to an email and maintain threading:
{ "subject": "Re: Original Subject", "body": "Your reply here...", "status": "pending"}The in_reply_to field should contain the Message-ID from the original email’s headers.
File naming
Section titled “File naming”Name your files with a timestamp prefix to ensure ordering:
1703808000-response.json1703808001-followup.jsonOr use any unique name — the platform processes all *.json files with "status": "pending".
Outbox directories
Section titled “Outbox directories”| Directory | Purpose |
|---|---|
/data/outbox/email/ | Drop pending emails here |
/data/outbox/sent/ | Successfully sent (moved here) |
/data/outbox/failed/ | Failed to send (moved here) |
Example: Agent sends a reply
Section titled “Example: Agent sends a reply”When you email your agent, it receives the message with metadata including the Message-ID. To reply:
cat > /data/outbox/email/$(date +%s)-reply.json << 'EOF'{ "subject": "Re: Your question", "body": "Here's my answer to your question.\n\nLet me know if you need anything else.", "status": "pending"}EOFThe platform picks this up and sends via Resend within seconds.
Watching the outbox
Section titled “Watching the outbox”The Router polls the outbox directory regularly:
- Checks for new files
- Validates JSON structure
- Sends via Resend API
- Moves file to
sent/orfailed/
Poll interval is approximately 5 seconds.
Common issues
Section titled “Common issues”Email not sending
Section titled “Email not sending”- Check the file is valid JSON
- Ensure
statusis"pending" - Verify
tois an array, not a string - Look in
/data/outbox/failed/for errors
Wrong recipient
Section titled “Wrong recipient”The to field must be an array:
// ❌ Wrong
// ✅ CorrectMissing email
Section titled “Missing email”If an email seems lost:
- Check
/data/outbox/sent/— it may have sent - Check
/data/outbox/failed/— it may have failed - Check spam folder on recipient’s end
Next steps
Section titled “Next steps”- Memory & persistence — How your agent remembers
- Architecture — Full system overview