E2E encrypted email relay for AI agents. Register an agent, get an email address, send and receive emails — all encrypted at rest with hybrid RSA+AES. The server never stores plaintext.
Agent (CLI) <--> ambox API (Cloud Run) <--> Resend (send + receive)
|
Firestore/MongoDB
(encrypted storage)
- Register — agent gets
{id}@ambox.dev, an API key, and an RSA-4096 private key (delivered once, never stored server-side) - Send — agent sends email via API, server relays through Resend, saves encrypted copy
- Receive — external email arrives at Resend, webhook hits ambox, server fetches body, encrypts with agent's public key, stores ciphertext
- Read — agent polls inbox, downloads encrypted emails, decrypts locally with private key
- Classify — incoming emails are classified (inbox/important/transactional/notification/spam) via LLM before encryption
Every registered mailbox also accepts plus-address aliases. Mail sent to
{id}+{tag}@ambox.dev is routed to the registered {id}@ambox.dev mailbox.
Use aliases for reproducible tests and per-run isolation, for example
mtro+autohost-20260605@ambox.dev. The original recipient address remains in
the stored email to field so callers can still identify the run/tag.
Every email is encrypted with AES-256-GCM using a per-message random key. The AES key is wrapped with the agent's RSA-4096 public key (OAEP/SHA-256). The server stores only ciphertext + wrapped key. Decryption happens exclusively on the client.
Deterministic nonces: 0x01 = subject, 0x02 = body, 0x03+ = attachments.
Base URL: https://ambox.dev/v1
All authenticated endpoints require Authorization: Bearer {api-key}.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/register |
Rate-limited by IP | Create agent, returns API key + RSA private key (once) |
POST |
/send |
API key | Send email via Resend |
GET |
/inbox |
API key | Poll encrypted emails |
DELETE |
/emails/{id} |
API key | Delete email |
PUT |
/emails/{id}/move |
API key | Move email to folder |
GET |
/emails/{id}/attachments/{filename} |
API key | Download encrypted attachment |
PUT |
/webhook |
API key | Configure push notification URL |
PUT |
/settings |
API key | Update TTL, display name |
GET |
/health |
None | Health check |
{
"agent_id": "my-agent",
"display_name": "My Agent",
"webhook_url": "https://my-server.com/hook",
"ttl_seconds": 604800
}Response (returned exactly once):
{
"agent_id": "my-agent",
"email": "my-agent@ambox.dev",
"api_key": "amk_live_...",
"private_key_pem": "-----BEGIN PRIVATE KEY-----\n..."
}{
"to": ["alice@example.com"],
"subject": "Hello",
"body_html": "<p>Hello from my agent</p>",
"body_text": "Hello from my agent",
"attachments": [
{
"filename": "report.csv",
"content_type": "text/csv",
"content": "Y2l0eSxjb2RlCg=="
}
]
}Attachment content must be Base64 encoded. Outbound messages accept up to 20
attachments and 35 MiB across the subject, body, and Base64 attachment content.
This leaves room below Resend's 40 MB encoded email limit. Outbound attachments
are encrypted before being saved to the sent folder and require GCS_BUCKET.
Query params: folder (inbox|sent|important|transactional|notification|spam), limit, since (RFC3339), cursor.
Returns encrypted emails. Decrypt client-side:
- RSA-OAEP decrypt
wrapped_keywith private key → AES key - AES-256-GCM decrypt
subject_encryptedwith nonce0x01 - AES-256-GCM decrypt
body_encryptedwith nonce0x02
# Install
npm install -g ambox
# Register
ambox register --agent-id my-agent
# List agents
ambox agents
# Check inbox (decrypts locally, saves to ~/.ambox/agents/{id}/inbox/)
ambox inbox
# Read email
ambox read msg_abc123
# Send email
ambox send alice@example.com "Hello" --body "Message body"
# Send one or more attachments
ambox send alice@example.com "Report" --body "Attached." --attach report.csv --attach chart.png
# Use specific agent
ambox --agent other-agent inboxEmails are decrypted and stored locally per agent, organized by folder and timestamp:
~/.ambox/
├── agents/
│ ├── my-agent/
│ │ ├── config.json
│ │ ├── private.pem
│ │ ├── inbox/
│ │ │ └── 20260416-152533_msg_abc123/
│ │ │ └── email.json
│ │ ├── sent/
│ │ └── important/
│ └── other-agent/
│ └── ...
└── default
- Runtime: Go 1.23+
- Email: Resend (outbound API + inbound webhooks)
- Database: Firestore Enterprise (MongoDB compatibility)
- Crypto: Go stdlib
crypto/*(RSA-4096, AES-256-GCM, argon2id) - Classifier: Ollama Cloud (OpenAI-compatible API)
- Hosting: Google Cloud Run
- CLI: Node.js (ES modules, zero framework)
make build # Build binary
make run # Run locally
make test # Unit tests
make test-race # Tests with race detector
make docker # Build Docker image
make deploy # Deploy to Cloud RunEnvironment variables:
| Variable | Description |
|---|---|
MONGODB_URI |
MongoDB connection string |
MONGODB_DATABASE |
Database name |
RESEND_API_KEY |
Resend API key (full access) |
RESEND_WEBHOOK_SECRET |
Resend webhook signing secret |
EMAIL_DOMAIN |
Email domain (default: ambox.dev) |
OLLAMA_API_KEY |
Ollama Cloud API key |
OLLAMA_BASE_URL |
Ollama endpoint (default: https://ollama.com/v1) |
OLLAMA_MODEL |
Classification model (default: qwen2.5:7b) |
GCS_BUCKET |
GCS bucket for attachments (optional) |
PORT |
Server port (default: 8080) |
MIT