Some limits come from Cloudflare, some from Amazon SES (only for domains that use it) and some from
Pylota Mail. Cloudflare’s and Amazon’s were read from their documentation on 2026-10-09 and can change.
pmail doctor reports the ones it can observe.
Account quota, set and raised by Cloudflare. It is not exposed to the Worker
Cloudflare
Queue backs off for up to 24 hours. An alert fires at 80% of PM_DAILY_SEND_QUOTA when you set it to your quota, otherwise on the first quota error (G3)
Mail domains per zone (routing + sending, including apex)
30
Cloudflare
Literal routing rules per domain (subdomain mail domains)
200
Cloudflare. So at most 200 addresses per subdomain mail domain. Apex domains use catch-all and have no limit
Literal routing rule matcher
90 characters
Cloudflare (Email Routing rules API, read 2026-10-09). An address on a subdomain mail domain longer than 90 characters is refused with 400 address_invalid
Catch-all
apex domains only
Cloudflare. This is why the platform domain must be a zone apex
Addresses per identity (all states)
20
Pylota Mail
Pending addresses per identity per domain
1
Pylota Mail
Address retirement grace
0–365 days, default 90
Pylota Mail
Forwarding test (inbound: forward)
The token must arrive within 10 minutes, otherwise forwarding is failed
At 9,000 the operator alert ses_identities_90pct fires and pmail doctor warns. At 10,000, adding a domain that needs an SES identity gets 422 transport_unavailable with details.reason = "ses_identity_limit". The count is the domains rows with ses_region set and not removed, plus the platform identity
When a pm-retired-{n} rule is full, the domain monitor opens the next one
Retired addresses bounced per deployment
75,000 (150 rules × 500)
Pylota Mail
Beyond it the oldest retired addresses leave the rules, and their mail is dropped without a bounce, like mail to an unknown address
SES API requests other than sends
1 per second per account and region; not adjustable
SES quotas (SES API sending quotas), read 2026-10-09
One deployment-wide token bucket (the SesControl Durable Object) admits one control-plane call per second. Domain create, PATCH and removal wait up to 5 s, then get 429 upstream_rate_limited with Retry-After; background checks wait up to 60 s, then retry later. Each SES domain’s daily identity check runs at a fixed time of day derived from a hash of its ID, so checks spread across the day (Domains on any DNS host §4.8)
Sending from the SES sandbox
200 messages per 24 hours, 1 per second, to verified addresses only
465 (TLS from the start) and 587 (STARTTLS) only. Any other port, 25 included: 400 smtp_port_not_allowed. A relay that offers no TLS: 422 smtp_tls_required, and the credentials are not sent
Cloudflare: “Workers cannot create outbound connections on port 25” (TCP sockets, read 2026-10-09); Pylota Mail
SMTP sends in parallel
4 per outbound consumer invocation
Cloudflare allows each invocation up to six connections waiting at once, and opening a socket counts (Workers limits, read 2026-10-09)
Connections per message
1, without pipelining
Pylota Mail
Timeouts
10 s to connect, 30 s per command, 60 s for the reply after the final dot
Pylota Mail. No reply after the final dot makes the send uncertain; it is never resent
Alignment probe
Before the first send and every day. On demand at most once a minute per domain (429 rate_limited). No probe back within 15 minutes is smtp_probe_timeout
7 MiB (a 5 MiB message after base64 decoding, plus JSON)
Requests per API key
600 per minute
Search per key
120 per minute
Agentic search per key
20 per minute. Tenant daily cap 500 by default
Sends per identity
120 per minute. Daily caps from policy
Signing per identity (RL_SIGN): agent assertions and signed HTTP requests together
600 per minute. Not counted against any plan allowance
Tenant creation and invitations per partner (RL_PARTNER)
10 per minute together, across all of the partner’s keys
Tenants per partner
max_tenants tenants that are not erased: 25 by default, set by the operator (403 partner_tenant_limit)
Rate-limit headers
Every authenticated response carries RateLimit-Limit (the bucket’s limit per period). A 429 also carries Retry-After and RateLimit-Reset, the seconds to the end of the bucket’s current period (for rate_limited the two are equal; other 429 codes set Retry-After to their own wait). No RateLimit-Remaining: the rate-limiting binding answers only allow or deny
Page size
25 by default, 100 maximum
Search limit
10 by default, 50 maximum
Search response size
256 KB. Above it, results are cut and truncated: true
50 a day (in the workspace’s time zone), all kinds except account and digest; further items go into one digest email at the next 09:00
Notification email per workspace
200 a day, all kinds except account and digest
new_mail, instant
A 2-minute hold after the first message, then at most one email per person and inbox every 10 minutes
new_mail, hourly and daily
One email at the top of each hour that had messages; one at 09:00 local time
needs_reply filter
Waits up to 5 minutes for triage
“Needs a person” email
Daily at 09:00 in the workspace’s time zone
Usage alerts
80% and 100% of each allowance; once per threshold per period for sends and triage; a 24-hour cooldown per feature and threshold for counts. None with PM_BILLING=off
Unsubscribe link
90 days, or until its link key leaves its 7-day window after a rotation
Retries while the platform domain is failing, or while the system identity’s submit is refused
On a deployment with billing on (Pylota Mail Cloud), the plan sets allowances for inboxes, sends, triage
analyses, custom domains, storage and seats. The table and the rules (holds, 402 billing_limit, top-ups,
resets) are in Plans and billing. Read your workspace’s live numbers with
GET /v1/usage. Self-hosted deployments have no plan limits; only the daily caps in tenant policy apply
(see API).
10 per code; the token is burned after 10 failures
Sign-in requests per client IP (RL_SIGNIN)
10 per minute, keyed by CF-Connecting-IP, across sign-in, sign-up and waitlist requests
Link and code lifetime
10 minutes, single use
Two-step verification codes
5 attempts a minute per person. 10 failures in a row lock two-step sign-in for 15 minutes
Recovery codes
10 per person, each single use. Generating new ones invalidates the old
Google or GitHub sign-in
10 minutes from start to callback, single use
Waitlist
An entry is written only when its confirmation link is used; an unused confirmation link expires after 10 minutes. An invite link (/console/sign-up?invite=…) is valid for 7 days, for the waitlisted address only. Entries are deleted 30 days after invitation
New workspace on Free (Pylota Mail Cloud)
At most 50 messages a day (the effective tenant_daily_send_cap is the policy value or 50, whichever is lower) for the first 7 days. A daily evaluation lifts the ramp from day 7 if bounce and complaint rates are under the auto-pause thresholds; otherwise it stays and is evaluated again each day. A paid plan lifts it at once. Above it: 429 daily_cap_reached