Skip to content

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.

If the email API fails, the file stays in the outbox. The platform retries.

Every outgoing email is a file you can inspect. Nothing hidden.

The agent doesn’t need email credentials or API access. Just write a file.

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/

To send an email, your agent creates a file like this:

{
"to": ["[email protected]"],
"subject": "Hello from your agent",
"body": "This is the email body.\n\nIt supports multiple paragraphs.",
"status": "pending"
}
FieldTypeDescription
tostring[]Array of recipient emails
subjectstringEmail subject line
bodystringPlain text email body
statusstringMust be "pending"
FieldTypeDescription
in_reply_tostringMessage-ID for threading
referencesstringEmail thread chain
ccstring[]CC recipients
bccstring[]BCC recipients

To reply to an email and maintain threading:

{
"to": ["[email protected]"],
"subject": "Re: Original Subject",
"body": "Your reply here...",
"in_reply_to": "<[email protected]>",
"status": "pending"
}

The in_reply_to field should contain the Message-ID from the original email’s headers.

Name your files with a timestamp prefix to ensure ordering:

1703808000-response.json
1703808001-followup.json

Or use any unique name — the platform processes all *.json files with "status": "pending".

DirectoryPurpose
/data/outbox/email/Drop pending emails here
/data/outbox/sent/Successfully sent (moved here)
/data/outbox/failed/Failed to send (moved here)

When you email your agent, it receives the message with metadata including the Message-ID. To reply:

Terminal window
cat > /data/outbox/email/$(date +%s)-reply.json << 'EOF'
{
"to": ["[email protected]"],
"subject": "Re: Your question",
"body": "Here's my answer to your question.\n\nLet me know if you need anything else.",
"in_reply_to": "<[email protected]>",
"status": "pending"
}
EOF

The platform picks this up and sends via Resend within seconds.

The Router polls the outbox directory regularly:

  • Checks for new files
  • Validates JSON structure
  • Sends via Resend API
  • Moves file to sent/ or failed/

Poll interval is approximately 5 seconds.

  1. Check the file is valid JSON
  2. Ensure status is "pending"
  3. Verify to is an array, not a string
  4. Look in /data/outbox/failed/ for errors

The to field must be an array:

// ❌ Wrong
// ✅ Correct

If an email seems lost:

  1. Check /data/outbox/sent/ — it may have sent
  2. Check /data/outbox/failed/ — it may have failed
  3. Check spam folder on recipient’s end