limits.md

Limits

Plan limits and how they are enforced, and inbox, address, TTL, message, attachment, batch, sandbox and retention limits.

Source: https://machinemail.dev/docs/reference/limits

These are the limits the API enforces.

Plans

Every plan has unlimited inboxes. Plans differ in how much mail and storage they include; see pricing for prices and overage rates.

Plan Messages included a month Daily limit Storage Custom domains Seats
Free 3,000, inbound and outbound combined 100 messages 1 GB 1 1
Pro 7,500 inbound, 2,500 outbound None 10 GB 10 Unlimited
Team 45,000 inbound, 15,000 outbound None 30 GB 50 Unlimited
Scale 200,000 inbound, 65,000 outbound None 150 GB 250 Unlimited

An outbound message counts once per recipient. Spam and quarantined mail never count. On Free, inbound mail past the allowance is held until it resets or you upgrade, and sending pauses until then. Upgrade in the dashboard under Billing.

How plan limits are enforced

What counts

Check your usage with GET /v0/organizations/usage, machinemail org usage, or the Usage page of the dashboard.

Free plan

Limit reached What happens
3,000 messages a month or 100 messages a day Sends are refused with 402 plan_limit_exceeded (errors), with resets_at and a Retry-After header. Nothing is sent. A send needing more recipients than are left is refused whole. Scheduled drafts stay scheduled and go out after the reset. Inbound mail is held.
1 GB of storage Inbound mail is held until you delete mail or drafts, or upgrade. Creating or growing a draft returns 402 plan_limit_exceeded (limit_key: "storage_bytes"). Sending still works.
1 custom domain Adding another domain returns 402 plan_limit_exceeded (limit_key: "custom_domains").
1 seat Inviting returns 402 plan_limit_exceeded (limit_key: "seats"). Members and pending invitations both use a seat.
10 trusted domains Adding another trusted domain returns 402 plan_limit_exceeded (limit_key: "trusted_domains"). Paid plans allow 500, and are refused the same way past them.

When both the monthly and the daily limit apply, the error reports the monthly one. A send to more recipients than the limit itself can never fit: its error has no resets_at.

Held mail

Held mail is stored but not delivered to your agents:

Held mail is delivered, oldest first, within about a minute of any of these:

On delivery, message.received fires as usual. The message's created_at is the delivery time and held_at is when it arrived. It joins its thread as of delivery, so replies that arrived meanwhile thread correctly.

Delivered mail counts toward the month and the day it is delivered in, so a backlog is delivered at most 100 messages a day (the daily limit), oldest first, while new mail queues behind it. Mail held for storage is delivered one message at a time as it fits next to the mail you keep.

Held mail limit Value
Held at once 3,000 messages or 1,000 MB per organization, whichever comes first. Inbound mail past it is not stored: message.rejected fires with reason: "plan_limit".
How long it is kept 35 days from the reset that could deliver it (mail held for storage: from its arrival). After that it is deleted and message.rejected fires with reason: "plan_limit_expired".

Paid plans

Pro, Team and Scale are never blocked. Usage past an allowance is counted and billed as overage at the rates on the pricing page: inbound per 1,000 messages, outbound per 1,000 recipients, storage per GB-month, and custom domains past the plan's number per domain-month. Spam and quarantined mail are never billed.

Already over a limit

Each account can own one Free organization. Creating another returns 402 plan_limit_exceeded (limit_key: "free_organizations"); organizations on a paid plan do not count, and joining other people's organizations is not limited.

Limits apply to new additions only. An organization with more custom domains or members than its plan allows (after a move to Free, for example) keeps them; it cannot add more until it is under the limit. An invitation is checked when it is sent, so an invitation sent earlier can still be accepted.

The agent sandbox has its own limits: plan limits do not apply to unclaimed sandbox inboxes. Once claimed, the inbox belongs to an organization and that organization's plan applies; the mail it received so far counts toward that month, and on Free a claim that does not fit in the month's allowance returns 402 plan_limit_exceeded and nothing moves.

Inboxes and addresses

Limit Value
Number of inboxes No limit on your own domains. Inbox count is not a quota.
Inboxes on the shared domain (machinemail.dev) 1,000 per organization. Past that, creating one returns 403 Inbox limit reached for <domain> (1000 per organization). Add your own domain for more.
Username 1 to 64 characters: a-z, 0-9, ., _, +, -; must start and end with a letter or digit. Input is trimmed and lowercased.
Username when omitted Random, 12 characters.
Address uniqueness Global. An address taken by any organization returns 409 Inbox with email <email> already exists.
Domain on create Defaults to the shared domain. Any other domain must belong to your organization (404 Domain not found otherwise).
Metadata String values. No size or key-count limit is enforced.

Addresses on the shared domain are first come, first served across all organizations. Mail to an unknown address on the shared domain is never turned into a new inbox.

Ephemeral inboxes

Setting Value
ttl_seconds Any positive number of seconds. There is no maximum.
ephemeral: true without ttl_seconds TTL of 3600 seconds (one hour).
Expiry expires_at = created_at + ttl_seconds. Every inbound message moves it to now + ttl_seconds (never earlier).
Changing it PATCH with a new ttl_seconds restarts the clock from now. ttl_seconds: null or ephemeral: false makes the inbox permanent.
Deletion An expired inbox is deleted with its messages, threads, drafts and files within about a minute (the reaper runs every 60 seconds), and inbox.expired is emitted.

Catch-all domains

A custom domain with catch_all: true creates an inbox the first time mail arrives for an unknown address, when all of these hold:

The new inbox has created_by: "catch_all". If the domain sets catch_all_ttl_seconds (any positive number), the inbox is ephemeral with that TTL; otherwise it is permanent. inbox.created is emitted.

Messages and attachments

Limit Value
Recipients per message At least one of to, cc, bcc, and at most 50 across all three, each address counted once (case-insensitive). An unclaimed agent sandbox inbox can have at most 5. More returns 400 Too many recipients: <n> across to, cc and bcc. A message can have at most 50 recipients; send separate messages instead. (param: to). Applies to sends, replies, reply-all, forwards, drafts (checked on create, update and send) and scheduled drafts (over the cap at send time: send_status: failed). An entry holding an address list (a@x.com, B <b@y.com>) counts, and is checked against suppressions and send lists, as each address in it. Each address must look like name@host.
Attachment by url Fetched by MachineMail: public http(s) only, no redirects, at most 10 MB, 20 second timeout.
Attachment by base64 content Valid base64; counts toward the per-message attachment limit.
Attachments per message At most 100, and 10 MB of files in total (decoded, content and url together). More is a 400 validation error with param: "attachments"; nothing is sent.
Request body (/v0) 16 MB. Larger bodies are refused with 400 before they are read.
Raw message injection (POST /inbound/raw) 40 MB.
Preview (preview field) First 200 characters.
Attachment download_url Expires one hour after the request.
Suppressed recipients Sending to an address in suppressions returns 403. Permanent bounces and complaints add addresses automatically.
Paused inbox Cannot send (403) or receive (mail is not stored; message.rejected is emitted).

Batches, lists and search

Limit Value
Page size (limit) Default 50, maximum 500. See Pagination.
batch-get, batch-update 100 message IDs per request; extra IDs are dropped.
Thread detail Up to 500 messages per thread in GET /v0/threads/{thread_id}. Thread label updates touch up to 500 messages.
Thread search Scans the newest 500 matching messages (truncated: true when more).
Inbox search (GET /v0/inboxes/search) Scans the newest 500 inboxes.
Allow and block lists Up to 500 entries per direction are applied to mail (the first 500 in entry order).
Metrics period 1 to 86400 seconds; default range the last 30 days; start must be before end; at most 100,000 events counted per query.
Export (GET /v0/inboxes/{inbox_id}/export) No limit: streams every message as mbox or jsonl.

Webhooks and events

Limit Value
Webhooks per organization No limit. Events are matched against the first 500 webhooks of an organization.
Webhook URL Public http(s); private and loopback addresses are refused.
Delivery timeout 10 seconds.
Retries 1 m, 5 m, 30 m, 2 h, 12 h, then daily, for up to 5 days. See Webhook events.
Stored response body First 1024 bytes.
SSE resume (Last-Event-ID) Up to 500 missed events.
SSE keepalive : ping after 15 seconds without events.
WebSocket idle timeout 120 seconds without traffic. Send {"type":"ping"} to keep it open.
MCP wait_for_message Up to 60 seconds per call (default 30).
MCP send_feedback message of 1 to 2,000 characters. See Rate limits.

Domains

Limit Value
Domains per organization Set by your plan: Free 1, Pro 10, Team 50, Scale 250. Paid plans can go over at $1 per domain per month (pricing).
Domain name A valid hostname, 3 to 253 characters.
Uniqueness A domain can belong to one organization only (409 Domain <domain> is already registered). The shared domain cannot be added (409 <domain> is a shared domain).
DNS checks PENDING and INVALID domains are re-checked in the background at most every 5 minutes, for 7 days after the domain was added. After that, call POST /v0/domains/{domain_id}/verify. Domain Connect discovery is cached for 6 hours (10 minutes after a provider error).

Pods

Agent sandbox

An agent that registers itself anonymously through auth.md gets, until a person claims it:

Limit Value
Inboxes One sandbox inbox on the shared domain, agent-<12 characters>@machinemail.dev
Sandbox inbox lifetime 24 hours
Messages 10 per rolling 24 hours
Different recipients 10 per rolling 24 hours (writing again to an address already counted is free)
Recipients per message 5 (to, cc and bcc together; never more than the cap for every message)
Scopes inboxes.read, messages.read, messages.send, messages.write on that inbox only
Claim window 24 hours
Access token lifetime 1 hour
Registrations 1 per rolling hour and 3 per rolling 24 hours per IPv4 address or IPv6 /64, and 10 per rolling 24 hours per IPv6 /48; about 100 per rolling hour and 1000 per rolling 24 hours across the service
Unclaimed sandboxes About 500 live across the service; new anonymous registrations wait while it is full

Every send counts when it is made: deleting sent messages or threads does not give any back. Sends to disposable email addresses (throwaway inboxes) can be refused with 403 and error.code disposable_email_not_allowed (param is the field holding the address: to, cc or bcc). Claiming the agent lifts the sandbox caps and this rule. See Rate limits.

Credentials

Item Lifetime
API key Until revoked, or until its expires_at
Dashboard session 30 days
Sign-in link 15 minutes, single use
First API key display (GET /auth/first-key) 10 minutes after first sign-in, once

Retention

Messages, raw MIME, attachments, events and webhook deliveries are kept until you delete them (or the inbox expires). There is no automatic retention window today.