Plans and Usage Tracking
Compare the limits of every plan, work within the Free plan's ramp, buy add-on capacity, and measure email activity and resource usage over time.
Compare limits across plans
Receiving email is free. Inboxes receive, store, and index mail without a billable event, so an inbound workflow costs nothing. Plans meter outbound sends and cap how many inboxes, domains, and members an organization can hold. Once an allowance is exhausted, creation is blocked.
| Limit | Free | Developer | Startup |
|---|---|---|---|
| Monthly price | $0 | $20 | $200 |
| Annual price (20 percent off, billed yearly) | Not offered | $192 | $1,920 |
| Inboxes | 3 | 10 | 150 |
| Domains | 0 | 10 | 150 |
| Members | 1 | 2 | 10 |
| Sends per 5 minutes | No published cap | 100 | 1,500 |
| Sends per day | 100 | 1,000 | 15,000 |
| Sends per month | No monthly cap | 10,000 | 150,000 |
| Mail storage | 3 GB | 10 GB | Not published |
| New-sender recipient ramp | 5 recipients in day one, 10 in week one | None | None |
Beyond the limits, Developer includes email support and Startup includes access to AgentMail’s SOC 2 report.
Read the live plan catalog
The canonical catalog, add-ons included, is published at an unauthenticated endpoint, the same source the Console and pricing page render from:
curl "https://api.agentmail.to/billing/catalog"No Authorization header is needed, and the path works with or without the /v0 prefix. The response lists every self-serve plan with its price and entitlements, where null means the plan has no cap on that field, plus the add-ons with unit prices in cents. An add-on is purchasable when its sellable flag is true. Responses are cached for about five minutes.
{
"version": 1,
"plans": [
{
"id": "developer",
"name": "Developer",
"description": "For developers making small scale applications",
"monthly_price_usd": 20,
"entitlements": {
"max_inboxes": 10,
"max_domains": 10,
"max_memberships": 2,
"max_five_minute_sends": 100,
"max_daily_sends": 1000,
"max_monthly_sends": 10000,
"max_first_day_recipients": null,
"max_first_week_recipients": null,
"max_tracking_domains": 0
},
"available": true,
"yearly": {
"lookup_key": "developer_yearly",
"price_usd": 192,
"discount_percent": 20,
"available": true
}
}
],
"addons": [
{
"lookup_key": "addon_sends",
"label": "Monthly sends",
"unit": "block",
"grants_per_unit": 1000,
"sellable": true,
"monthly": { "unit_amount": 200, "currency": "usd", "available": true },
"yearly": {
"lookup_key": "addon_sends_yearly",
"unit_amount": 1920,
"currency": "usd",
"available": true
}
}
]
}Read this endpoint instead of hardcoding plan numbers into your own tooling, so a price or limit change reaches you without a redeploy.
Check your organization’s effective limits
The limits live on the organization record, so read them from the API instead of assuming the catalog numbers. Use this before provisioning to catch resource exhaustion early, and alert when a count approaches its limit.
agentmail organizations getThe response pairs each resource count with its limit and tells you which plan the enforcement comes from:
inbox_countagainstinbox_limitanddomain_countagainstdomain_limitare the provisioning headroom. Compare them before a batch of creates.billing_plan_idis the plan behind the numbers.five_minute_send_limitanddaily_send_limitare the send windows enforced for this organization.- A limit field that is absent means the organization has no cap on that resource.
These are the effective values enforcement uses, not copies of the catalog. Add-ons, support grants, and account state all adjust them, so an organization’s numbers can differ from the plan table above.
{
"organization_id": "1a2b3c4d-5e6f-4a1b-8c2d-3e4f5a6b7c8d",
"authentication_type": "clerk",
"authentication_id": "org_1a2b3c4d5e6f7a8b9c0d",
"billing_plan_id": "free",
"inbox_limit": 3,
"domain_limit": 0,
"five_minute_send_limit": 1000,
"daily_send_limit": 100,
"inbox_count": 2,
"domain_count": 0,
"tracking_allowed": false,
"name": "Example organization",
"clerk_organization_id": "org_1a2b3c4d5e6f7a8b9c0d",
"created_at": "2026-08-10T09:15:24.312Z",
"updated_at": "2026-08-24T18:02:41.559Z"
}Start on the Free plan
Your sign-up path decides the plan you start on:
- Signing up through the Console starts your organization on Free. Free needs no credit card.
- Signing up through the CLI agent flow starts it on the Agent plan: 1 inbox, 0 domains, 1 member, and 10 sends per day, with no five-minute or monthly cap and no recipient ramp. These are pre-verification limits, not a tier you stay on. Verifying with the 6-digit code emailed at sign-up moves the organization to Free at no cost, and limit errors on the Agent plan carry a
fixthat points at that verification call.
Free’s enforced send quota is the daily one. Over a month it works out to about 3,000 emails, the number the pricing page advertises.
Send within the recipient ramp
Organizations on the Free plan start with a cap on how many different people they can cold-email, separate from the send quotas:
- at most 5 distinct recipients in the organization’s first 24 hours
- at most 10 distinct recipients, cumulative, in its first week
After 7 days the ramp lifts entirely. Paid plans have no ramp, and upgrading removes the cap immediately rather than at the window boundary. The Agent plan is also exempt, since its 10-per-day send cap is the control there.
The cap counts people, not addresses or messages:
- Addresses that have emailed one of your inboxes first never count. Your agent can always answer its inbound mail.
- Variants of one mailbox count once: plus-tag forms like
you+test@example.comand Gmail dot variants all occupy a single slot. - Recipients are counted when the send is attempted, so a send that fails later still used its slots.
If your first week involves emailing more than a handful of new contacts, upgrade before the launch instead of engineering around the ramp.
Handle rate limits and buy add-ons
Two kinds of limit apply to every outbound send:
- A period quota caps the total sends accepted in a window (daily and monthly).
- A rate limit protects a short burst window (five minutes).
Both apply at once, so meeting a monthly forecast does not guarantee that a burst will be accepted. Estimate against your busiest window, not just your monthly total.
How sends are counted:
- Quotas are counted per organization, across all of its inboxes.
- Each recipient counts as one send. A message with three addresses across
to,cc, andbccconsumes three sends. - Replies are outbound sends. Include them in your estimate.
The windows are fixed rather than rolling. The daily window resets at midnight UTC, and the monthly quota uses a fixed 30-day window, not the calendar month. When your organization crosses 80 percent of its monthly send allowance, AgentMail emails you a one-time usage warning for that window.
When a send is rejected for a quota or rate limit, the API returns HTTP 429 with the error code rate_limit_exceeded. The body names the window that bound, its cap, and the upgrade that raises it, and the response carries three headers: Retry-After (seconds until the window resets), ratelimit-limit (the cap), and ratelimit-reset (same seconds as Retry-After).
{
"name": "RateLimitError",
"code": "rate_limit_exceeded",
"message": "Daily send limit exceeded",
"fix": "Your plan's daily send limit is 100, and resets in 28800s (also returned in the Retry-After header). Wait for the window to reset, or upgrade at https://console.agentmail.to/dashboard/upgrade — Developer raises it to 1000 for $20.00/month.",
"resource": "send",
"window": "daily",
"limit": 100,
"upgrade_url": "https://console.agentmail.to/dashboard/upgrade",
"docs": "https://docs.agentmail.to/errors#rate_limit_exceeded"
}Handle a 429 by honoring Retry-After and backing off exponentially (the SDKs retry these for you). Branch on window before you let a retry loop run: a five-minute rejection clears in minutes, while retrying before a daily or monthly quota resets will not create additional capacity, so treat those as terminal for the window and alert instead.
Habits that keep high-volume agents inside the limits:
- Distribute sends across inboxes. Your organization’s quota does not change, but mailbox providers throttle per address, so 10 emails each from 100 inboxes lands better than 1,000 from one.
- Make retries safe. Pass
client_idon create operations so a retry cannot duplicate a resource, covered in errors and idempotency. For sends, track themessage_idof what you already sent, or stage the message as a draft and send the draft, so a retry cannot email the same person twice. - Watch usage before launches. The Console and the usage metrics both show send volume against your window.
Developer and Startup customers can buy extra capacity on top of their plan, one unit at a time, instead of jumping tiers:
- +1 inbox for $2 per month
- +1 custom domain for $2 per month
- +1,000 monthly sends for $2 per month
Buy as many units of each as you need. A Developer organization that wants 15 inboxes and one extra domain pays $20 + $10 + $2 = $32 per month. Add-ons are available on the paid plans, so a Free organization that needs a custom domain upgrades to Developer first.
The flow is self-serve in the Console, under Billing: drag the slider for the resource you are adding, review the prorated amount for the rest of the billing period, and pay. Increases are charged pro rata and take effect instantly, enforced on the very next API call. Decreases are scheduled for the end of the billing period, and you can cancel a pending decrease any time before it takes effect. Annual subscriptions buy annual add-on units at the same 20 percent discount, which works out to $1.60 per month per unit billed yearly.
Send add-ons raise more than the monthly number. The daily and five-minute limits are recomputed from the new effective monthly allowance, at one tenth per day and one hundredth per five minutes, and never drop below the plan’s base values. Five send blocks on Developer, for example, make the allowance 15,000 per month, 1,500 per day, and 150 per five minutes.
Add-on limits are enforced at the same three points as plan limits: inbox creation, domain creation, and send.
When add-ons are not enough, choose the upgrade path based on what is about to run out:
- If your forecast fits the next listed allowance, move to that plan.
- If you need more members, move to the plan whose member allowance covers your team, or arrange an enterprise agreement.
- If Startup’s allowances do not cover the required burst, daily volume, monthly volume, or resource count, arrange an enterprise usage-based agreement with negotiated rates and limits.
The usage-based model is for hand-negotiated enterprise workloads, not a self-serve selection. Its standard entitlement model has no fixed inbox, domain, member, five-minute, daily, or monthly cap, though a negotiated agreement can still set limits for deliverability or operational reasons. Enterprise agreements also unlock everything in Startup plus unlimited inboxes, bulk discounts, a white-label platform, EU region cloud, bring-your-own-cloud deployment, dedicated IPs, OIDC and SAML SSO, an SLA, and a dedicated Slack support channel. Contact support@agentmail.cc for enterprise pricing and limits. Add-ons are not a substitute for a negotiated delivery policy when your required sending pattern exceeds the Startup plan’s limits.
Measure your usage
AgentMail keeps two read-only series about your account, and each answers a different kind of question:
- Event counts tell you what happened during a time window: how many emails were sent, delivered, bounced, or received, grouped by event type and time bucket. Reach for them to investigate whether activity changed during a period.
- Usage totals tell you what a scope holds over time: running totals of storage, messages, threads, inboxes, domains, and pods. A point is the total at that time, not the change since the previous point.
| Question | Query | Result |
|---|---|---|
| How many delivery or mailbox events happened during a window? | Event counts | Event types mapped to { timestamp, count } buckets |
| How much of a resource exists over time? | Usage totals | Usage types mapped to { timestamp, value } points |
Both queries share the same time controls. They default to the last 24 hours, and history goes back 90 days: a start older than that is rejected, and a future end is clamped to the current time.
Every metrics query requires the metrics_read permission:
- A key created without a
permissionsobject holds every permission,metrics_readincluded. - A key created with a
permissionsobject holds only what the object sets totrue, so include"metrics_read": true. A permission omitted from the object or set tofalseis denied. - The key’s resource scope still applies.
metrics_readlets a key read metrics within its own organization, pod, or inbox scope, not beyond it.
Count email events over a window
Results come back grouped by event type and time bucket. This example counts sent and bounced mail in one-hour buckets over the last 24 hours.
Every parameter is optional, and all of them go in the query string:
curl "https://api.agentmail.to/v0/metrics/events?event_types=message.sent,message.bounced&period=3600" \
-H "Authorization: Bearer $AGENTMAIL_API_KEY"| Param | Type | What it means |
|---|---|---|
event_types | string[] | The event types to count, comma separated in the URL. Omit it to count every type. |
start | RFC 3339 timestamp | Beginning of the window. Defaults to 24 hours ago, and must be within the last 90 days. |
end | RFC 3339 timestamp | End of the window. Defaults to now. A future value is clamped to now. |
period | integer | Bucket size as a whole number of seconds, 1 through 86400. Omit it for a single total per event type across the window. |
limit | integer | The most buckets returned per event type, 1 through 1000. Defaults to 1000. |
descending | boolean | Newest buckets first. Defaults to false, oldest first. |
event_types accepts these values:
message.receivedand its flagged variantsmessage.received.spam,message.received.blocked, andmessage.received.unauthenticatedfor inbound mailmessage.sent,message.delivered,message.bounced,message.complained, andmessage.rejectedfor the stages of outbound mailmessage.openedfor the first open of a tracked messagedomain.verifiedfor a custom domain completing verification
These are the same events webhooks deliver, and the event table describes when each one fires.
The response has one key per queried event type, each holding { timestamp, count } buckets. How to read them:
- A bucket’s timestamp is the end of its window, so the
10:00:00Zbucket below counts events between 09:00 and 10:00. - The newest bucket closes at the query time instead of on the grid. That bucket is still filling, which is why its timestamp is not a round number.
- Buckets where nothing happened are skipped, so quiet hours show up as gaps rather than zeros, and a type with no matching events at all comes back as an empty list.
- Without
period, each type gets a single bucket holding its total for the whole window.
{
"message.sent": [
{ "timestamp": "2026-08-25T09:00:00Z", "count": 36 },
{ "timestamp": "2026-08-25T10:00:00Z", "count": 18 },
{ "timestamp": "2026-08-25T10:41:12.583Z", "count": 3 }
],
"message.bounced": []
}Compare the message.sent, message.delivered, and message.bounced buckets for the same window to watch delivery health. Sends that stop turning into deliveries, or a growing bounce count, are an early sign of a deliverability problem.
Requests that push past the limits behave like this:
- With a
periodset, a window that spans more than 1000 buckets is still served, but only for the 1000 buckets nearest the oldest edge (nearest the newest edge withdescending=true). Use a largerperiod, or shiftstartandendto page through the rest.
GET /v0/metrics without the /events suffix is a deprecated alias for this query, at every scope. Existing calls keep working, but point new code at /v0/metrics/events and the queryEvents and query_events methods.
Track what a scope holds over time
The usage query returns running totals. Use it when the question is about amounts rather than activity: how much storage the mail takes up, or how many messages, threads, inboxes, domains, or pods exist. This example reads daily storage and message totals.
curl "https://api.agentmail.to/v0/metrics/usage?usage_types=storage_bytes,message_count&period=86400" \
-H "Authorization: Bearer $AGENTMAIL_API_KEY"usage_types takes the place of event_types. The other parameters keep the names, types, and defaults of the event query, with one difference: period here defaults to 600, so a call with no parameters returns the last 24 hours at 10-minute resolution.
Which usage types carry data depends on the scope you query:
| Queried scope | Usage types |
|---|---|
| Inbox | storage_bytes, message_count, thread_count |
| Pod | The inbox types plus inbox_count and domain_count |
| Organization | The pod types plus pod_count |
Omit usage_types to get every type valid for the queried scope. A requested type that does not apply to the scope is dropped from the response without an error, so asking an inbox for pod_count returns no such key.
The response has one key per usage type, each holding { timestamp, value } points where value is the cumulative total at that moment. Between changes the series holds flat rather than skipping buckets, so any point reads as the total at that time. The newest point is stamped at the query time and carries the current total, which makes descending=true with limit=1 the quickest way to read usage right now. A type the scope holds nothing of comes back as an empty list.
{
"storage_bytes": [
{ "timestamp": "2026-08-25T00:00:00Z", "value": 453786 },
{ "timestamp": "2026-08-25T10:41:14.207Z", "value": 560307 }
],
"message_count": [
{ "timestamp": "2026-08-25T00:00:00Z", "value": 134 },
{ "timestamp": "2026-08-25T10:41:14.207Z", "value": 168 }
]
}To judge inbox_count or domain_count against the caps your plan enforces, read them next to your organization’s effective limits.
Query one pod or inbox
The paths above read at your key’s scope. An organization key gets numbers for the whole organization, while a key scoped to a pod or inbox automatically gets its own pod or inbox. To pin a query to one pod or inbox explicitly, put its id in the path:
GET /v0/pods/{pod_id}/metrics/eventsandGET /v0/pods/{pod_id}/metrics/usageread one podGET /v0/inboxes/{inbox_id}/metrics/eventsandGET /v0/inboxes/{inbox_id}/metrics/usageread one inbox
The parameters, permission, and response shapes are the same at every scope, and the SDK methods keep their names under client.inboxes.metrics and client.pods.metrics:
# events for one inbox
curl "https://api.agentmail.to/v0/inboxes/example@agentmail.to/metrics/events?event_types=message.received&period=3600" \
-H "Authorization: Bearer $AGENTMAIL_API_KEY"
# usage for one pod
curl "https://api.agentmail.to/v0/pods/<pod_id>/metrics/usage?usage_types=inbox_count&period=86400" \
-H "Authorization: Bearer $AGENTMAIL_API_KEY"A pod_id comes from creating or listing pods, covered in multi-tenant email.
{
"inbox_count": [
{ "timestamp": "2026-08-25T00:00:00Z", "value": 3 },
{ "timestamp": "2026-08-25T10:41:16.331Z", "value": 3 }
]
}To match the query to the check:
- For capacity monitoring, query usage on the scope where the limit or resource decision applies, then compare consecutive points to see whether the totals are growing.
- For an investigation into event volume, request the relevant event types with a
periodthat matches the incident window instead of deriving activity from usage totals. - Use the inbox path to investigate a single inbox and the pod path to check a whole tenant, so the result covers that pod’s inboxes without mixing in another pod’s.