API rate limits and sending quotas
Requests per second, sending quotas by plan and the maximum sizes of each resource.
#Requests per second
Each team has a limit of requests per second, summed across all its keys. The value depends on the plan:
| Plan | Requests per second |
|---|---|
| Free | 5 |
| Pro | 10 |
| Scale | 20 |
| Enterprise | 50 |
Values refreshed from GET /plans when the page opens.
The limit in effect for your team appears in GET /usage (rate_limit). Apps that hold only a sending key can see it too: GET /usage accepts sending_access keys, and the X-RateLimit-* headers come with every response, including sends. Every response carries these headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per second. |
X-RateLimit-Remaining | How many more fit in the current second. |
Retry-After | Only on 429: seconds until you can try again. |
X-RateLimit-Reset | Only on 429: when the limit resets (Unix timestamp). |
Go over the limit and the response is 429 rate_limit_exceeded. To send many emails, use batch sending: 100 emails count as one request.
The limit belongs to the team, not to the key, and also applies to the MCP server. The SDKs read the headers for you: postal.rateLimit (Node.js) and $postal->rateLimit() (PHP) keep the limit of the last response, even with a sending-only key, and wait for Retry-After before retrying a 429.
#Sending quotas
Sends count when the email is created (including scheduled emails and template tests). Each broadcast recipient, each broadcast test and each automation email also counts. The day and the month are counted in UTC.
| Plan | Per day | Per month |
|---|---|---|
| Free | 100 | 3,000 |
| Pro | Unlimited | 50,000 |
| Scale | Unlimited | 100,000 |
| Enterprise | Unlimited | Custom |
Values refreshed from GET /plans when the page opens.
When you reach the quota, sends receive 429 daily_quota_exceeded or 429 monthly_quota_exceeded (over SMTP, 452 4.5.3). A broadcast that hits the quota midway is paused and resumes by itself when the quota renews; an automation send fails, and so does the run. On plans that allow it, you can turn on sending beyond the quota in the dashboard, under Billing: extra emails are charged per thousand, with no blocking. Your plan stays the same; you only pay for the extra packs, on the next invoice. Track your usage in GET /usage.
Account reputation. The daily limit in force is the lower of the plan's and the ceiling of the account reputation: a score from 0 to 100 that starts at 0 for new accounts and goes up with each day of sending without bounces or complaints. At levels 1 to 4 the ceiling is 500, 2,000, 5,000 and 10,000 emails a day; at level 5 only the plan's limit applies. The limit in force is in emails.daily.limit of GET /usage, with plan_limit (the plan's) and limited_by (plan or reputation), and the reputation block has the score, the level and the next level. When the reputation is what limits, the message of the 429 daily_quota_exceeded gives the level and the ceiling.
Sends add-on. When the team's plan has a monthly limit and offers the Sends add-on, the owner or an admin buys, with a one-time payment, a reserve of sends that is only used once the monthly quota runs out: first the plan's quota, then the reserve (one credit per email, the ones expiring soonest first), then sending beyond the quota, if the team turned it on, and only then the refusal. Broadcasts do not use the reserve, emails to test addresses take no credit, and the plan's daily limit still applies. The message of the 429 monthly_quota_exceeded says where to buy when that is possible. See Sends add-on and the API reference.
Sending reputation
To protect delivery for everyone, Postal pauses a team's sending when, over the last 7 days, permanent bounces exceed the limit of the account reputation level (3% at level 1, 4% at level 2 and 5% from level 3 up), spam complaints exceed 0.3% or content rejected by the providers exceeds 5% of the emails sent. The rates are judged from 100 sends in the period at levels 1 to 3 and from 1,000 at levels 4 and 5. At levels 1 to 3, Postal also looks at the last 24 hours, and bounces of every type above 15% also pause. In the first 24 hours after the team's first send, a pause for permanent bounces only happens above twice the limit. The test addresses are not counted. Each pause takes 40 points off the account reputation. The pause email lists up to 10 addresses with the most recent bounces (they are already suppressed). While sending is paused, sends receive 403 sending_paused. Request a review in the dashboard (there is no public endpoint for it) or through the contact form to have it lifted.
How to get a warning first and how to detect the pause: when permanent bounces reach the level's warning (2% at level 1, 2.5% at level 2 and 3% from level 3 up) or complaints reach 0.15%, the team owners and administrators get an email warning before the pause (one when a rate crosses its level; the next only if it worsens by 1 percentage point, or 0.1 for complaints, or after 7 days); when sending pauses, they get another. Through the API, subscribe to the webhook events sending.paused and sending.resumed to alert your team right away, or query GET /usage, which returns sending.paused, paused_at, the reason and review_requested_at (when a review was requested). A batch of invitations or emails with wrong addresses is the most common cause: validate addresses before sending and handle the bounces you receive in webhooks.
Suppressed addresses: a permanent bounce puts the address on the suppression list automatically. If the person has fixed their email, remove it with DELETE /suppressions/{id}, or in bulk with POST /suppressions/batch/remove, and sending works again.
#Features by plan
Plans and pricing: postal.so/precos or, through the API, GET /plans (public, no key needed).
| Free | Pro | Scale | Enterprise | |
|---|---|---|---|---|
| Domains | 3 | 10 | 1,000 | Unlimited |
| Inboxes | 3 | 10 | 100 | Unlimited |
| Inboxes on your own domain | No | Yes | Yes | Yes |
| Team members | 3 | 10 | Unlimited | Unlimited |
| AI credits per month | 10 | 100 | 1,000 | Unlimited |
| Automation runs per month | 1,000 | 10,000 | 100,000 | Unlimited |
| Contacts (marketing plan) | 1,000 | 5,000 | 50,000 | Unlimited |
| Segments (marketing plan) | 3 | Unlimited | Unlimited | Unlimited |
| Broadcasts per month (marketing plan) | Unlimited | Unlimited | Unlimited | Unlimited |
| Layouts | 10 | Unlimited | Unlimited | Unlimited |
| A/B tests in automations | No | Yes | Yes | Yes |
| Email validation credits per month (marketing plan) | 100 | 5,000 | 50,000 | Unlimited |
| CRM connections (marketing plan) | 0 | 1 | 5 | Unlimited |
| App integrations (marketing plan) | 1 | 3 | 10 | Unlimited |
Values refreshed from GET /plans when the page opens.
Teams can have their limits adjusted individually. The value that applies to you is always the one in GET /usage.
The volume scale is made of bands defined by Postal (for example 3,000, 50,000, 100,000 and 250,000 emails per month): each plan serves some of those bands, with the price of each, and above the last band only the custom plan serves. The list of bands and the ones each plan serves is in GET /plans (volume_bands and bands).
#Plan limits and features
Each plan sets, per team, how many things you can have and which features are included. Going over the limit for an item, or using a feature the plan does not include, answers 403 plan_limit_reached, with a message that says which one. The full list, with the text of each feature and whether the API already enforces it (enforced), comes in GET /plans (feature_catalog), and your team's values in GET /usage. For numbers, null means unlimited and 0 means not included; for on/off features, 1 means included and 0 not.
Quantity
They only block creating one more item. What already exists is never deleted.
Contacts above the limit. The contact limit of the marketing plan can also be exceeded, like the email one: under Billing, the team owner or an admin turns on Keep adding contacts above the plan limit (one switch per product, off by default). When it is on, imports, forms, the API and the CRM integrations stop refusing with 403 plan_limit_reached at the limit, and the extra contacts are charged per pack of 1,000 (rounded up) on the next invoice, without changing plans. What is charged is the most contacts the team had in the billing period (or the contacts that received campaigns in the cycle, if higher: see below), and deleting contacts afterwards does not lower what was already measured. Unsubscribed contacts and suppressed addresses count as contacts, as always. It only works with an active marketing subscription, on a plan that allows overage and has a price for extra contacts; otherwise the limit keeps refusing. The refusal message says how to turn it on when the option is available.
Contacts add-on. When the team's marketing plan offers the Contacts add-on, the owner or an admin adds contacts to the limit without changing plans: the pack's price goes on the subscription's invoice and the limit becomes the plan's plus the packs'. The limit the API checks (403 plan_limit_reached) and the one GET /usage shows already include the packs. They only apply to a paid, active marketing plan. See Contacts add-on and the API reference.
Contacts that receive campaigns in the cycle
The marketing plan's contact limit (with the Contacts add-on) also applies to who receives campaigns: in the team's usage cycle (the month that renews on your plan's day), campaigns and automation emails can go to up to that many different contacts. Each address counts once per cycle. Contacts that already received a campaign in the cycle keep counting until the renewal, even if deleted, and deleting and creating the same address again does not count twice. Deleting contacts that have not received a campaign yet frees room right away. Sending several campaigns to the same contacts does not use more. Transactional emails do not count.
Example, with a limit of 50,000 contacts: sending 5 campaigns to the same 50,000 contacts in the cycle is within the plan. Deleting the 50,000 that already received one and importing another 50,000 in the same cycle goes over the limit: the campaign to the new ones is refused until the renewal.
A campaign that would go over the limit is refused as a whole, without sending anything, with 422 cycle_recipients_limit, a message with the numbers, the renewal date and what to do, and the cycle_recipients block (new, used, limit, remaining and resets_at). Postal checks again when a scheduled campaign is about to go out: if it no longer fits, it is paused with the same reason. An automation email to a contact that has received nothing in the cycle, with the limit full, is skipped. The owner and admins get an email at 80% and at 100%. With contact overage turned on, nothing is refused and the contacts above the limit are billed as extra contacts. Without a contact limit, there is no recipient limit. The counter is in GET /usage (marketing_recipients) and in Settings › Usage, and what a campaign would do, before sending, in GET /broadcasts/audience.
| Key | What counts | Where it blocks |
|---|---|---|
domains | Sending domains | POST /domains and domain claims |
inboxes | Inboxes | POST /inboxes |
templates | Templates | POST /templates and duplicating |
layouts | Layouts | POST /layouts and duplicating |
api_keys | API keys | POST /api-keys |
webhooks | Webhook endpoints | POST /webhooks |
team_members | Team members (pending invitations count) | Inviting people, in the dashboard |
contacts | Contacts | Creating contacts through the API, an import, a form or an integration |
segments | Segments | POST /segments and merging segments |
topics | Topics | POST /topics |
forms | Forms | POST /forms |
automations | Automations (active or not) | POST /automations |
broadcasts_per_month | Broadcasts sent or scheduled in the month (UTC) | Sending or scheduling a broadcast |
crm_connections | CRM connections | Creating the connection |
app_integrations | App integrations (Stripe, Supabase, Clerk, Firebase, Auth0) | Creating the integration |
attachments_mb | Total size of an email's attachments, in MB. 0 allows no attachments; empty (null) uses the platform limit of 40 MB | Sending emails with attachments |
Other monthly limits do not give a 403: emails_per_day and emails_per_month give 429 (see sending quotas); ai_credits, 429 ai_credits_exhausted; email_verifications_per_month, 403 if the plan does not include email validation and 429 verification_credits_exhausted when the credits run out; and automation_runs: above the limit, new automation runs do not start (they are marked skipped).
On/off features
| Key | What it unlocks | Where it blocks |
|---|---|---|
batch_sending | Batch sending | POST /emails/batch |
scheduled_sending | Scheduled sending | scheduled_at in POST /emails |
test_addresses | Simulated test addresses | Sending to @sandbox.postal.so |
smtp_access | SMTP sending | SMTP refuses the message with 550 5.7.1 |
engagement_tracking | Open and click tracking | Turning on open_tracking or click_tracking on a domain (domains already tracking keep tracking) |
custom_domain_records | Custom Return-Path and tracking subdomain | New or changed custom_return_path and tracking_subdomain |
shared_email_links | Shared email links | POST /emails/{id}/share |
custom_inbound_domains | Inboxes and receiving on your own domain | Creating inboxes and enabling capabilities.receiving on your own domains |
inbox_agent | Inbox AI agent | Turning the agent on in an inbox |
exports | CSV exports | POST /exports and the export of form submissions |
mcp_access | MCP server | The /mcp endpoint |
restricted_api_keys | Sending-only or domain-restricted keys | POST /api-keys with permission: "sending_access" (keys already created keep working) |
dynamic_segments | Dynamic segments | Creating a dynamic segment (existing ones keep working) |
form_analytics | Form analytics | GET /forms/overview and GET /forms/{id}/analytics, with the answers by question and the analytics export |
automation_experiments | A/B tests in automations | A/B experiment steps |
postal_branding | “Sent with Postal” badge (inverted: 1 means it has the badge) | Hiding the badge on a form |
retention_days, priority_support, uptime_sla and dedicated_manager are informative: the API blocks nothing because of them (retention is 30 days for everyone, and support is arranged separately).
When the plan drops
When the plan changes, the amount the team already has can go over the new limit. Nothing is deleted: of domains, inboxes, templates, layouts, API keys, webhooks, automations, segments, topics and forms, the oldest ones up to the limit stay active and the newest ones get paused_by_plan: true. A paused item shows up in lists and can be read and deleted, but not changed or used: the domain does not send, the API key does not authenticate, the webhook receives no events, the template does not send, the form goes offline, the automation does not fire, the segment cannot be used as an audience and the inbox does not run the agent (it keeps receiving email). Any attempt to change it answers 403 paused_by_plan. Items come back on their own when the plan, the volume tier or the count allows it, and GET /usage lists what is paused in paused. Contacts, team members and connections are not paused: the limit only prevents adding more.
The monthly email quota and the number of contacts follow the volume tier you subscribed to (see Plans), and a team can have limits adjusted individually. The value that applies to you is always the one in GET /usage.
#Sizes and quantities
| What | Limit |
|---|---|
Recipients in to | 50 per email |
Emails per batch (POST /emails/batch) | 100 |
| Attachments | 40 MB in total per email (less if the plan caps it: attachments_mb) |
| Attachments of inbox replies, messages and drafts | Up to 10 files and 40 MB in total |
| Message over SMTP | 40 MB and 50 recipients |
Scheduling (scheduled_at) | Up to 30 days ahead |
Test addresses ([email protected]…) | They do not count toward the sending quota or toward the bounce and complaint rates used for reputation; the requests-per-second limit still applies |
| Tags | 50 per email; name and value up to 256 characters |
| Subject and header values | 998 characters |
| Items per page in lists | 100 |
Idempotency-Key | 256 characters, kept for 24 hours |
Images (POST /assets) | 5 MB |
| Exports in progress | 3 per team |
Public link of an email (POST /emails/{id}/share) | Valid from 1 minute to 48 hours |
Metrics (GET /emails/metrics) | Up to 10,000 periods and 100 IDs in each filter |
DNS instructions by email (POST /domains/{id}/instructions) | Up to 5 recipients; 3 per minute and 10 per hour per team; 5 per day for each address |
| Replies forwarded per email with reply detection | 20 |
Sources to skip on a webhook (skip_sources) | 20 |
| Contact imports | CSV up to 20 MB; 3 in progress per team |
Contacts per request in POST /contacts/batch | 10,000 |
| Tags per contact request | 50 |
| Contacts per bulk tag operation | 1,000 |
| Email validation | Up to 200,000 addresses per list; 2 lists in progress per team |
| Dynamic segment | 10 condition groups and 20 conditions in total |
| Segments in a merge | 20 |
| Contact properties | 100 per team; text values up to 1,000 characters |
| Variables of a template | 50 |
| Submissions to a public form | 5 per minute per IP |
| Broadcast scheduling | Up to 30 days ahead |
| Broadcast test | 5 addresses |
Events (POST /events) | Name up to 100 characters; properties up to 16 KB |
| Automations | 100 steps; 10 filters on the trigger; 20 actions per update_contact; waits of up to 365 days |
| Automation webhook step | 3 attempts, 15 seconds each |
| Retention of emails, webhooks and logs | 30 days |
| Event retention | 90 days |
| Export files | 7 days; the download link is valid for 1 hour |
| Email validation results | 7 days for each address's result and the CSV; the validation itself is kept for 30 days |