# Rate Limits

Source: https://molted.email/docs/rate-limits

Monthly, daily, and hourly send limits and how to check your current usage.

Molted Email enforces rate limits across three time windows to protect deliverability and prevent abuse.

## Rate limit windows

| Window | Reset cycle |
| - | - |
| **Monthly** | Resets on the 1st of each month (UTC). |
| **Daily** | Resets at midnight UTC. |
| **Hourly** | Resets at the top of each hour (UTC). |

Limits for each window depend on your plan (see [Billing & Plans](https://molted.email/docs/billing.md) for prices and everything else a plan includes):

| Plan | Monthly | Daily | Hourly |
| - | - | - | - |
| Trial (before a plan is picked; sends are blocked) | 100 | 50 | 20 |
| A paid plan's 3-day trial | 100 in total for the trial | 50 | 20 |
| Free | 3,000 | 100 (ramps up, see [Free plan ramp](#free-plan-ramp)) | 40 |
| Solo (and legacy Starter) | 10,000 | 1,000 | 200 |
| Team (and legacy Growth) | 50,000 | 5,000 | 1,000 |
| Enterprise | Unlimited | Unlimited | Unlimited |
| Expired | 0 | 0 | 0 |

The budget is per tenant, shared by every mailbox; there is no per-mailbox send allowance. A tenant with no quota row falls back to 100 a month, 10,000 a day and 1,000 an hour.

## Checking usage

With an API key, check your current usage with `GET /v1/agent/budget`:

```bash title="CLI"
molted budget
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/budget" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

MCP: `check_budget` (no parameters), or the `molted://budget` resource. A signed-in human gets the same send windows from `GET /v1/me/usage` (cookie session; an API key gets `401` there).

### Response

```json title="Response (Solo)"
{
  "tenantId": "tenant_abc123",
  "monthly": { "used": 420, "limit": 10000, "remaining": 9580 },
  "daily": { "used": 12, "limit": 1000, "remaining": 988 },
  "hourly": { "used": 3, "limit": 200, "remaining": 197 },
  "teamAddressSends": { "used": 4 },
  "inbound": { "used": 310, "limit": 5000, "remaining": 4690, "held": 0, "heldReason": null, "overAllowanceBehavior": "overage" },
  "negativeSignals": { "count": 0, "budget": 50, "remaining": 50 },
  "timestamp": "2026-10-03T12:00:00.000Z"
}
```

Past the monthly allowance on a plan with overage, `monthly` also carries `inOverage`, `overageCount`, `hardCap` and `hardCapRemaining`. `negativeSignals` is the tenant-wide count of bounces and complaints in the last 24 hours against its budget of 50 (see below). `inbound` is `null` when it can't be read.

### Stored limits

`GET /v1/ops/quotas` returns the quota row stored for your workspace, as it is in the database (snake\_case): `tenant_id`, `daily_send_limit`, `hourly_send_limit` and `monthly_send_limit`, plus the other per-workspace caps that row holds, such as `overage_hard_cap`, `max_mailboxes`, `max_domains` and `storage_limit_bytes` (a cap that isn't set is `null`). A workspace with no row gets `{ "tenant_id": "...", "daily_send_limit": 10000, "hourly_send_limit": 1000, "monthly_send_limit": 100 }`, the fallback described above. Any valid API key can read it; there is no CLI command or MCP tool for it, and none of your keys can change it.

Use it to see the limits themselves. It says nothing about what you have used, and it doesn't show the Free ramp's current step. For usage and the limit in force right now, read `GET /v1/agent/budget`.

### Free plan ramp

A new Free account's daily limit ramps up over its first two weeks (counted from when Free was activated): 20 a day in week 1, 50 a day in week 2, then the plan's 100 a day. During the ramp, `daily.limit` is the ramp step, `daily.planLimit` the plan's daily cap, and `daily.freeRamp` the details:

```json title="Response (Free, week 1)"
{
  "daily": {
    "used": 12,
    "limit": 20,
    "remaining": 8,
    "planLimit": 100,
    "freeRamp": {
      "step": 0,
      "dailyLimit": 20,
      "startedAt": "2026-10-01T09:00:00.000Z",
      "complete": false,
      "held": false,
      "holdReason": null,
      "nextStepDailyLimit": 50,
      "nextStepAt": "2026-10-08T09:00:00.000Z"
    }
  }
}
```

The ramp only steps up while your sending is healthy. If your bounce rate is above 5% (from 20 sends on) or your complaint rate above 0.1%, or a mailbox was auto-paused for its reputation, the account stays at its current step (`held: true`, `holdReason` says which) until the rate recovers. Hitting the ramp's daily limit blocks with `daily_limit_exceeded`, like any daily limit. Accounts that were already on Free before the ramp shipped skip it.

## Sends to your team are free

A send to a **verified team address** doesn't count toward the monthly or daily limits, the trial's total, or overage, on every plan including Free. It still counts toward the hourly limit, and every other check (suppression, dedupe, cooldown, consent, approvals) applies as usual, so an agent that emails its owner about an outage can't flood them.

Verified team addresses are your members' own verified emails, plus notification addresses you add and someone confirms through the link we email to them. An address that isn't confirmed yet counts like any other recipient. You can have 3 on Free, 10 on Solo and 25 on Team (members' emails included, and an address you removed keeps counting for 30 days). After a downgrade, your members' emails stay free and only the oldest confirmed addresses that fit the new limit do. We send at most 5 confirmation emails a day per workspace and 1 an hour per address, and a workspace that can't send yet (or any more) can't add addresses.

### Team addresses

Add the addresses an agent may email for free, such as an on-call inbox:

```bash
molted team-addresses list
molted team-addresses add oncall@acme.com   # sends a confirmation link to that address
molted team-addresses remove <id>
```

Or `GET` / `POST /v1/agent/notification-addresses` and `DELETE /v1/agent/notification-addresses/:id`, with a key that covers all mailboxes. There is no MCP tool for team addresses. `molted budget` shows `teamAddressSends` for the month. A confirmation link is valid for 72 hours. Errors: `403 team_address_cap_reached` (with the `cap`), `429 confirmation_rate_limited` (with `retryAfterSeconds`), and `403 sends_blocked` on a workspace that can't send. Hosted MCP (OAuth) apps can't add addresses (`403 oauth_forbidden`).

## What happens when a limit is exceeded

When a window's limit is reached, subsequent send requests are blocked with a reason naming that specific window -- never the generic `rate_limited` (see [Errors & Policy Blocks](https://molted.email/docs/errors.md#quota) for when that one _does_ show up, as a fallback):

```json
{
  "requestId": "req_abc123",
  "status": "blocked",
  "reason": "hourly_limit_exceeded"
}
```

`reason` is one of `hourly_limit_exceeded`, `daily_limit_exceeded`, `monthly_limit_exceeded`, or `overage_cap_exceeded`, matching whichever window was hit. The request is not queued: wait for the window to reset or upgrade your plan.

On Solo, Team, the legacy Starter and Growth plans and Enterprise (once a paid plan's trial is over), passing the monthly allowance doesn't block: sends continue as billed [overage](https://molted.email/docs/billing.md#overage-and-credits) up to a hard cap, twice the monthly allowance unless your account has its own cap. Past that the reason is `overage_cap_exceeded`. Free and trials have no overage, so the monthly allowance is a hard stop (`monthly_limit_exceeded`).

Two more tenant-wide stops:

- `free_sends_paused` -- Molted has paused sending for every Free account (for example during abuse on the shared infrastructure). Temporary; paid plans are unaffected.
- `negative_signal_budget_exceeded` -- 50 or more bounces plus complaints across the tenant in the last 24 hours blocks every send until the count drops.

## What counts toward quota

Only a send that is actually accepted for delivery consumes quota, whether it was sent directly through the API, from a journey step, or as a scheduled follow-up -- all three draw from the same monthly/daily/hourly budget:

- A send that is queued or scheduled (including a warmup-deferred slot).
- A held send (mailbox autonomy level 1 or 2, or a per-thread risk hold) once a human approves it and it reaches the queue.
- A journey `send` step or a scheduled follow-up sent at mailbox autonomy level 3 (auto-send). A journey step or follow-up held for approval (level 1 or 2) only consumes quota once a human approves it, same as any other held send.

Journey and follow-up sends split the limit into temporary and permanent reasons:

- `hourly_limit_exceeded` / `daily_limit_exceeded` (and `free_sends_paused`) are temporary -- the window resets on its own, so the send is deferred and retried automatically once it does (a journey step re-enqueues and the run does not advance yet; a follow-up moves its `scheduled_at` out and stays `pending`). Nothing is recorded as blocked for a deferral, since nothing permanent happened.
- `monthly_limit_exceeded` / `overage_cap_exceeded` are permanent for the rest of the billing period. A journey step is recorded across the `send_requests` row (flipped to `blocked`), a `policy_decisions` row with the reason, a `mail.send.blocked` audit event, and the `journey.step_completed` stream event's `skipReason` -- then the journey keeps advancing to its next step. A follow-up is recorded the same way (`send_requests` row blocked, a policy decision) and its own record is cancelled with `cancel_reason` set to the same reason, the same way a suppressed or billing-blocked follow-up is cancelled.

The following never consume quota, even though a `send_requests` row is created for them:

- A send blocked by policy (suppression, cooldown, disengagement, an active opportunity, a paused tenant/mailbox, warmup or domain throttling, `rate_limited` itself).
- An idempotent retry that hits an existing `dedupeKey` (returns the original result, `duplicate`).
- A held send that is instead rejected, or that expires unanswered.

Sends can also be blocked for billing reasons:

- `trial_not_activated` -- the account hasn't picked a plan yet.
- `subscription_expired` -- the paid plan ended: cancelled, a paid trial that didn't convert, or a failed payment whose grace period ran out.

## Storage quota

In addition to send rate limits, each plan has a storage quota that limits total message body and attachment storage. When the storage limit is exceeded, attachment uploads are refused with a plain `403` (there is no reason code):

```json
{
  "statusCode": 403,
  "message": "Storage limit exceeded. Used: 1073741824 bytes, limit: 1073741824 bytes.",
  "error": "Forbidden"
}
```

Existing data remains accessible. See [Storage & Attachments](https://molted.email/docs/storage.md) for details on storage limits and retention.

## Request throttling

The API itself has no per-key request rate limit; the limits on this page are send limits. The exceptions are sign-up (3 an hour per IP) and sign-in and password reset (10 per 15 minutes per IP and per email), plus the per-route limits documented on their own pages (for example [inbound hooks](https://molted.email/docs/inbound-hooks.md#security-model) and alert test sends).

Opening a message in the portal or the phone app renders it on the server, so those views are limited too: `GET /v1/me/messages/:id/view` and `GET /v1/me/send-approvals/:id/view` (signed-in people only, never an API key) share a budget of 60 views a minute per person, counted on each API server. Over it, the API answers `429` with a `Retry-After` header and `{ "error": "rate_limited", "message": "Too many message views. Try again shortly.", "retryAfterSeconds": <n> }`. Agents are not affected: they read messages through `/v1/agent/threads/:id` and `/v1/agent/messages/:id/raw`.

Related caps: per-sending-domain rate limits and the warmup schedule live on [Domains](https://molted.email/docs/domains.md#domain-rate-limits).

## Best practices

- Monitor usage with `molted budget` (`GET /v1/agent/budget`) before large batch sends. A batch holds at most 500 sends.
- Spread sends across hours to avoid hitting hourly limits.
- If you regularly hit limits, consider upgrading your plan.
