Robotomail mascotThis page is for AI agents

Set up your email

Follow these steps to create a Robotomail account and get a real email address you can use to send and receive email autonomously.

Base URL: https://api.robotomail.com

01

Create an account

One POST creates your account, your default mailbox, and returns an API key. Save the api_key.key value immediately — it is only shown once. If lost, the account owner can sign in and create a replacement (see Key Recovery below). The slug becomes your permanent platform email (slug@robotomail.co) and cannot be changed. The key does nothing until your human verifies the signup email (product calls return 403 (email verification required)). Verification enables your permanent Free plan: 1 mailbox, 10 sends and 10 receives per month. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. No credit card, no expiry. Allowances reset on the first of each month (UTC). Upgrade for higher limits or a custom domain. Mail outside the Free plan rules or above the receive limit is discarded, not saved for later. Upgrade at checkout (step 09) for higher limits or a custom domain. Paid plans are billed when you upgrade, with a 30-day money-back guarantee. A verification email was sent to the signup address.

POST /v1/signup
POST /v1/signup
Content-Type: application/json

{
  "email": "you@company.com",
  "password": "a-secure-password",
  "slug": "my-company"
}

# Response (201):
# {
#   "user": { "slug": "my-company", "platform_email": "my-company@robotomail.co" },
#   "api_key": { "key": "rm_a1b2c3d4..." },
#   "mailbox": {
#     "id": "b3a2c801-7d9f-4e1a-8c52-1f0a9d3e4b6c",
#     "address": "my-company",
#     "fullAddress": "my-company@robotomail.co",
#     "status": "ACTIVE"
#   },
#   "mailbox_limit": 1,
#   "daily_send_limit": 10,
#   "monthly_send_limit": 10,
#   "email_verified": false,
#   "next_steps": {
#     "verify_email": "Ask your human to click the link sent to the signup email. Required before the API key works.",
#     "add_payment": "1 mailbox, 10 sends and 10 receives per month. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. No credit card, no expiry. Allowances reset on the first of each month (UTC). Upgrade for higher limits or a custom domain. Optional: POST /v1/billing/upgrade returns a checkout URL for your human to review.",
#     "add_domain": "POST /v1/domains",
#     "send_email": "POST /v1/mailboxes/:id/messages"
#   }
# }
#
# Header: X-RateLimit-Remaining: <number>
02

Your mailbox is ready

Your default mailbox (slug@robotomail.co) was created automatically at signup. All requests use the Authorization header. Until your human verifies the signup email, product calls (creating webhooks, uploading attachments, sending) return 403 (email verification required). After verification you're on the permanent Free plan: 1 mailbox, 10 sends and 10 receives per month. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. No credit card, no expiry. Allowances reset on the first of each month (UTC). Upgrade for higher limits or a custom domain. Solo includes one mailbox total and one custom domain; Starter includes 10 mailboxes in total across platform and custom domains. Adding a custom domain does not increase that allowance; choose a higher plan for more mailboxes. Sending also requires the verified email; a 403 there means your human still needs to click the verification link. Set a display name so your emails show "Your Name" instead of just the email address.

GET /v1/mailboxes
GET /v1/mailboxes
Authorization: Bearer rm_a1b2c3d4...

# Response (200):
# {
#   "mailboxes": [
#     {
#       "id": "b3a2c801-7d9f-4e1a-8c52-1f0a9d3e4b6c",
#       "address": "my-company",
#       "fullAddress": "my-company@robotomail.co",
#       "status": "ACTIVE",
#       "dailySendLimit": 10,
#       "monthlySendLimit": 10,
#       "receivedThisMonth": 4
#     }
#   ]
# }
# receivedThisMonth is the per-mailbox inbound count for the current
# calendar month (UTC). The account-level cap is the sum across mailboxes.

# Set a display name (emails will show "Hal" <my-company@robotomail.co>):
PATCH /v1/mailboxes/:id
Authorization: Bearer rm_a1b2c3d4...
Content-Type: application/json

{
  "displayName": "Hal"
}
03

Send email

Free allows 10 sends per calendar month, one recipient per message including To, CC and BCC. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. Threading is automatic: include inReplyTo (camelCase) to continue a conversation. Send only to people who have agreed to receive your messages. Solo includes 50 sends/day and 500 sends/month. Starter includes 500 sends/day and 15,000 sends/month per mailbox. Check recorded recipient permission before sending, stay within its scope, and stop on opt-out. Your application must enforce these rules; a paid plan or available quota does not establish permission. Acceptable Use Policy.

POST /v1/mailboxes/:id/messages
POST /v1/mailboxes/:id/messages
Authorization: Bearer rm_a1b2c3d4...
Content-Type: application/json

{
  "to": ["recipient@example.com"],
  "subject": "Hello from my agent",
  "bodyText": "This is the email body."
}
04

Receive email

Three options: (A) Register a webhook for real-time push notifications — best if your agent has a public URL. Webhook payloads are { event, timestamp, data } objects with snake_case fields. (B) SSE for real-time streaming — no public URL needed, works behind firewalls. Use the CLI or connect directly. (C) Poll the messages endpoint on a schedule — simplest option for batch processing. Polling returns stored message objects with camelCase fields. Note: the formats differ — see the docs for the exact shapes. Inbound messages with files (PDFs, images, etc.) include an attachments[] array on the payload — webhook/SSE payloads ship a ready-to-use download_url for each file (presigned R2 URL, valid 24h). For inline images, match content_id against cid: references in body_html.

Receive email
# Option A: Webhooks (real-time push)
POST /v1/webhooks
Authorization: Bearer rm_a1b2c3d4...
Content-Type: application/json

{
  "url": "https://your-server.com/webhook",
  "events": ["message.received"],
  "headers": {
    "Authorization": "Bearer your-token"
  }
}

# Response (201):
# {
#   "webhook": {
#     "id": "wh_abc123",
#     "url": "https://your-server.com/webhook",
#     "events": ["message.received"],
#     "secret": "a1b2c3d4e5f6..."
#   }
# }
# Save the secret — it is only returned on creation.

# Option B: SSE (real-time stream — no public URL needed)
# Use the CLI: robotomail listen --forward-to http://localhost:4000/hook
# Or connect directly:
GET /v1/events?events=message.received
Authorization: Bearer rm_a1b2c3d4...
Accept: text/event-stream
# Events stream as Server-Sent Events. Reconnect with Last-Event-ID
# to replay missed events. See docs for all 5 event types.

# Option C: Polling (simplest for batch processing)
GET /v1/mailboxes/:id/messages?direction=INBOUND&since=2026-03-11T00:00:00Z&limit=10
Authorization: Bearer rm_a1b2c3d4...

# Poll every 10-30 seconds. Use direction=INBOUND and since
# with an ISO timestamp to get new inbound messages.
# Track the latest createdAt to avoid reprocessing.

# ─── Inbound attachments ────────────────────────────────────────
# Webhook/SSE message.received payload shape:
# {
#   "event": "message.received",
#   "timestamp": "2026-03-18T12:00:00.000Z",
#   "data": {
#     "message_id": "...",
#     "from": "...",
#     "body_html": "<img src=\"cid:logo@acme\" />...",
#     "attachments": [
#       {
#         "id": "att_...",
#         "filename": "invoice.pdf",
#         "content_type": "application/pdf",
#         "size_bytes": 204800,
#         "content_id": null,
#         "download_url": "https://...r2.cloudflarestorage.com/...?signed-24h"
#       }
#     ]
#   }
# }
#
# Fetch each attachment with HTTP GET on download_url — no auth header needed.
# For inline images (content_id != null), rewrite cid:<content_id> in body_html
# to download_url before passing the HTML to a renderer.
#
# REST list/get does NOT include download_url — call GET /v1/attachments/:id
# to fetch a fresh presigned URL (also use this if a download_url has expired).
#
# Drop limits: max 20 attachments per message, 25 MB each. If exceeded,
# attachments_dropped: true and attachments_dropped_reason: "size"|"count"|"both".
#
# ─── Quota-gated payloads (over monthly inbound limit) ───────────
# Paid monthly inbound caps: Solo 500 / Starter 2,000 / Growth 20,000 /
# Scale unlimited. When your account is over its cap, every subsequent
# inbound message still fires a message.received event — but the data
# object drops to a stub (identity fields only, no body/attachments):
# {
#   "event": "message.received",
#   "timestamp": "2026-03-18T12:00:00.000Z",
#   "data": {
#     "over_limit": true,
#     "message_id": "...",
#     "mailbox_id": "...",
#     "mailbox_address": "...",
#     "received_at": "...",
#     "account": {
#       "inbound_usage": { "current": 2001, "limit": 2000, "percentage": 100, "reset_date": "2026-05-01T00:00:00.000Z", "status": "limit_reached" },
#       "upgrade": { "browser_url": "https://robotomail.com/billing", "api_endpoint": {"method":"POST","path":"/v1/billing/upgrade"}, "hint": "..." }
#     }
#   }
# }
# Branch on data.over_limit === true. Stubs carry NO from/to/subject/body/attachments.
#
# While over-limit: GET /v1/mailboxes/:id/messages/:msgId and
# GET /v1/attachments/:id return 402 INBOUND_LIMIT_EXCEEDED until
# you upgrade or the monthly reset lands. List endpoints
# (GET /v1/mailboxes/:id/messages, /threads) still return 200 —
# they hide over-limit messages and expose the count at
# metadata.overLimitCount. Upgrade with POST /v1/billing/upgrade;
# previously gated messages become readable via the list endpoint.
# They are NOT retroactively redelivered via webhook/SSE.
#
# List envelope metadata (GET messages / GET threads):
# {
#   "metadata": {
#     "overLimitCount": 3,
#     "upgrade": {                    # always present (camelCase)
#       "browserUrl": "https://robotomail.com/billing",
#       "apiEndpoint": {"method":"POST","path":"/v1/billing/upgrade"},
#       "hint": "..."
#     },
#     "limitHint": {                  # present once usage ≥ 50 % (snake_case,
#       "current": 1600,                # matches webhook/SSE inbound_usage)
#       "limit": 2000,
#       "percentage": 80,
#       "reset_date": "2026-05-01T00:00:00.000Z",
#       "status": "near"              # approaching | near | limit_reached
#     }
#   }
# }
#
# Approaching the limit (≥ 50 %): full payloads carry a
# data.account.inbound_usage block (no upgrade sub-field yet).
# Use it to surface a countdown.
05

Verify webhook signatures

Every webhook payload includes an X-Robotomail-Signature header. Compute HMAC-SHA256 of the raw request body using your webhook secret and compare.

Webhook verification
# Verification pseudocode:
expected = HMAC-SHA256(webhook_secret, raw_request_body)
signature = request.headers["X-Robotomail-Signature"]
valid = timing_safe_equal(expected, signature)

# Reject the payload if valid is false.
06

Error handling

The API returns structured errors. Always check the status code and handle these common cases programmatically.

Common error responses
# 403 (email verification required) — verify the signup email before product API calls.
#
# 402 TRIAL_RECIPIENT_LOCKED — recipient is outside the Free rules, or more than one recipient.
# Free allows 10 sends per calendar month, one recipient per message including To, CC and BCC. Exchange mail only between your agent and your verified email address. Upgrade to email anyone.
# 402 TRIAL_SEND_LIMIT — all 10 sends for this UTC month are used.
# The legacy TRIAL_* codes stay stable. The response includes resetsAt.
# Wait for renewal or upgrade; account and saved-message access stay available.
# Free allows 10 receives per calendar month. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. Mail outside these rules or above the receive allowance is discarded, with no backlog on renewal or upgrade.

# 402 — Paid/legacy plan held inbound (INBOUND_LIMIT_EXCEEDED)
# Returned by GET /v1/mailboxes/:id/messages/:msgId and GET /v1/attachments/:id
# when the requested inbound resource was received after the cap was reached.
# {
#   "error": "Monthly inbound limit reached — upgrade to unlock this message",
#   "code": "INBOUND_LIMIT_EXCEEDED",
#   "resource": { "type": "message", "id": "..." },
#   "upgrade": { "browserUrl": "...", "apiEndpoint": { "method": "POST", "path": "/v1/billing/upgrade" } },
#   "resetAt": "2026-05-01T00:00:00.000Z"
# }
# Headers: Retry-After (seconds to reset), X-Robotomail-Retriable: false,
#   X-Robotomail-Inbound-Quota: 2001/2000, X-Robotomail-Inbound-Warning: reached.
# → Do NOT retry on backoff. Upgrade or wait for the monthly reset.
# → List endpoints still return 200 with over-limit messages hidden and counted in metadata.overLimitCount.
# → Webhook/SSE payloads for over-limit messages arrive as stubs with data.over_limit: true (no body, no attachments).

# 429 — Rate limited (daily or monthly send limit exceeded)
# {
#   "error": "Daily send limit reached",
#   "upgrade": "POST /v1/billing/upgrade to get a checkout URL"
# }
# → Wait until midnight UTC for daily reset, the 1st of the month for monthly reset, or choose a plan with higher limits: Solo allows 50/day and 500/month; Starter allows 500/day and 15,000/month per mailbox.

# 403 — Forbidden (email not verified)
# {
#   "error": "Email verification required before sending. Check your inbox."
# }
# → Ask the account owner to verify their email.

# 403 — Mailbox limit reached
# {
#   "error": "Starter plan limited to 10 mailboxes. Upgrade to create more.",
#   "upgrade": "POST /v1/billing/upgrade to get a checkout URL"
# }
# → Upgrade to paid, or delete an existing mailbox.

# 409 — Conflict (duplicate resource)
# {
#   "error": "A mailbox with this address already exists"
# }
# → Use a different address, or list existing mailboxes.
07

Check account status

Call GET /v1/account to see your current plan, limits, verification state, and what actions are needed. Check this endpoint when you hit unexpected errors to understand your account state.

GET /v1/account
GET /v1/account
Authorization: Bearer rm_a1b2c3d4...

# Response:
# {
#   "account": {
#     "id": "usr_abc123",
#     "email": "you@company.com",
#     "emailVerified": true,
#     "slug": "my-company",
#     "plan": "developer",
#     "suspended": false,
#     "suspendedReason": null,
#     "storageUsedBytes": 0,
#     "storageLimitBytes": 10737418240,
#     "mailboxCount": 1,
#     "activeMailboxCount": 1,
#     "sentToday": 12,
#     "sentThisMonth": 240,
#     "receivedToday": 5,
#     "bouncedToday": 0,
#     "monthlyInboundCount": 42,
#     "monthlyInboundLimit": 2000,
#     "monthlyInboundPercentage": 2,
#     "monthlyInboundResetAt": "2026-05-01T00:00:00.000Z",
#     "overLimitMessageCount": 0
#   }
# }
#
# Requires a full-access API key (scoped keys get 403).
# Use this to check verification, plan, daily/monthly send usage,
# monthly inbound quota, and suspension status.
08

Key recovery

If you lose your API key, you can create a new one by signing in with your email and password. The new key is returned once — persist it immediately. Revoke the lost key afterwards.

Key recovery
# 1. Sign in with email/password to get a session
POST /api/auth/sign-in/email
Content-Type: application/json

{
  "email": "you@company.com",
  "password": "a-secure-password"
}

# 2. Create a new API key (uses session cookie from sign-in)
POST /v1/api-keys
Cookie: <session cookie from step 1>
Content-Type: application/json

{
  "name": "replacement-key"
}

# Response: { "key": "rm_newkey...", "prefix": "rm_newk" }

# 3. Revoke the lost key
DELETE /v1/api-keys/:id
Authorization: Bearer rm_newkey...
09

Upgrade your plan

Verify the account email before using product endpoints. Free never expires: 10 sends and 10 receives renew monthly. Reaching a cap does not block account or saved-message access. Sends over the monthly cap return 402 TRIAL_SEND_LIMIT with resetsAt. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. Upgrade for higher limits or ongoing access to other correspondents. Request a checkout URL and share it with the account owner to unlock full access on a paid plan. Paid plans are billed when you upgrade, with a 30-day money-back guarantee and no paid-plan trial. Existing paid subscriptions change plans through the billing dashboard and Stripe Customer Portal; this endpoint returns 409 if you are already on a paid plan. Pass the API plan identifier solo, developer (shown as Starter), growth or scale to select a tier. The URL expires in 24 hours. After payment, access to other correspondents unlocks automatically. Mail discarded on Free cannot be recovered. Held paid-plan inbound messages become readable when the account is within its allowance.

POST /v1/billing/upgrade
POST /v1/billing/upgrade
Authorization: Bearer rm_a1b2c3d4...
Content-Type: application/json

{"plan": "solo", "period": "monthly"}
# Plan IDs: "solo" = Solo ($5/mo or $60/year), "developer" = Starter ($19/mo), "growth" = Growth ($79/mo), "scale" = Scale ($199/mo)
# Use "period": "annual" for yearly billing. Solo has no annual discount.
# Omit body to default to "developer".
#
# New session (201):
# {
#   "checkout_url": "https://checkout.stripe.com/c/pay/...",
#   "expires_at": 1741737600,
#   "message": "Share this URL with the account owner..."
# }
#
# Existing session (200) — safe to call repeatedly.
# Already paid? Returns 409.
10

Delete account

Permanently delete the account and all associated data (mailboxes, messages, domains, attachments, webhooks, API keys). Requires full-access authentication (unscoped API key or dashboard session — scoped keys are rejected). A confirmation body is required. External service cleanup (storage, email provider, billing) is best-effort after the database deletion.

DELETE /v1/account
DELETE /v1/account
Authorization: Bearer rm_a1b2c3d4...
Content-Type: application/json

{
  "confirm": "DELETE"
}

# Response (200):
# { "deleted": true }
#
# This is irreversible. All data is permanently removed.
# 400 — Missing or invalid confirmation body
# 403 — Scoped key (requires full-access)

Plans, prices and limits

  • Free: $0 — 1 mailbox, 10 sends and 10 receives per month. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. No credit card, no expiry. Allowances reset on the first of each month (UTC). Upgrade for higher limits or a custom domain.
  • Solo: $5/month or $60/year — 1 mailbox, 50 sends/day and 500 sends/month per mailbox, 500 receives/month per account, 1 custom domain, 1 GB storage.
  • Starter: $19/month or $168/year — 10 mailboxes, 500 sends/day and 15,000 sends/month per mailbox, 2,000 receives/month per account, 1 custom domain, 10 GB storage.
  • Growth: $79/month or $708/year — 50 mailboxes, 1,000 sends/day and unlimited sends/month per mailbox, 20,000 receives/month per account, 5 custom domains, 25 GB storage.
  • Scale: $199/month or $1788/year — 200 mailboxes, 2,000 sends/day and unlimited sends/month per mailbox, unlimited receives/month per account, unlimited custom domains, 100 GB storage.
  • Solo costs $60/year, the same as 12 monthly payments of $5, with no annual discount. With the current annual deal, Starter, Growth and Scale cost about 50% less than 12 monthly payments. The discount is applied automatically at checkout when you choose annual billing.
  • Mailbox limits include all platform and custom-domain mailboxes. Solo allows one mailbox total; adding a domain does not add a second mailbox.
  • Paid send limits return 429 when exceeded. Paid inbound above the monthly cap is held: single-read endpoints return 402 INBOUND_LIMIT_EXCEEDED, list endpoints hide held messages and count them in metadata.overLimitCount, and webhook/SSE events are stubs with over_limit: true. An upgrade or monthly reset unlocks held messages. Free discards mail above its receive limit.