Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Limits

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.

Mail

LimitValueSourceWhat happens
Inbound message size25 MiB through Email Routing; 40 MB, including headers, through Amazon SES (domains with inbound = ses)Cloudflare Email Routing; SES quotas, read 2026-10-09Never reaches the Worker: Cloudflare rejects it, and SES stores at most 40 MB in S3
Outbound message size, encoded, including attachments5 MiBCloudflare Email Sending413 message_too_large, or a signed link if large_attachments: link
Recipients per message (to + cc + bcc)49, default policy 10. Cloudflare allows 50; one is kept for the hidden journal copy of Message-ID strategy BCloudflare / Pylota Mail / policy400 too_many_recipients
Subject length998 charactersRFC 5322 / Cloudflare400 invalid_request
Custom headers on a send16 KB total; at most 20 non-X- headers, service-set ones included; values ≤ 2,048 bytes. Names, matched case-insensitively: X- names matching ^X-[A-Za-z0-9_-]+$ (≤ 100 bytes), or Importance, Priority, Sensitivity, Keywords, Comments, Organization (sent in that casing). Values of Importance: high, normal, low; Priority: normal, non-urgent, urgent; Sensitivity: personal, private, company-confidentialCloudflare (Email headers reference, read 2026-10-10)Checked when the request arrives: 400 header_not_allowed for a name, 400 invalid_request for a value
Attachments per send32 (REST); 10 per call in the MCP tool mail_sendPylota Mail400 invalid_request
Inbound MIME nesting depth32Pylota MailDeeper parts are kept raw; flag parse_degraded
Inbound MIME parts500Pylota MailFurther parts are kept raw; flag parse_degraded
Attachment text extracted20 MB input, 200 pages, 2 MB textPylota Mailtext_status: unavailable beyond it
Archive expansion checkedratio ≤ 100:1, ≤ 100 MBPylota MailLarger means risk: archive_bomb, quarantined
Local part length64 characters, including the thread tokenRFC 5321Username plus suffix at most 40
References kept on our replies20: the first plus the 19 most recentPylota MailThe ones between are trimmed (C2)
Daily sendingAccount quota, set and raised by Cloudflare. It is not exposed to the WorkerCloudflareQueue 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)

Domains and addresses

LimitValueSource
Mail domains per zone (routing + sending, including apex)30Cloudflare
Literal routing rules per domain (subdomain mail domains)200Cloudflare. So at most 200 addresses per subdomain mail domain. Apex domains use catch-all and have no limit
Literal routing rule matcher90 charactersCloudflare (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-allapex domains onlyCloudflare. This is why the platform domain must be a zone apex
Addresses per identity (all states)20Pylota Mail
Pending addresses per identity per domain1Pylota Mail
Address retirement grace0–365 days, default 90Pylota Mail
Forwarding test (inbound: forward)The token must arrive within 10 minutes, otherwise forwarding is failedPylota Mail

Amazon SES

Applies to domains connected with dns_records, send_only, or smtp_relay with inbound: ses (Domains on any DNS host). SES quotas are per AWS region.

LimitValueSourceWhat happens
Inbound message size40 MB, including headersSES quotas, read 2026-10-09Larger messages are not stored in S3 and never reach the Worker
Verified identities per region10,000 (raised only through the AWS account manager)SES quotas, read 2026-10-09At 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
Rules per receipt rule set200, not adjustableSES quotas, read 2026-10-09Pylota Mail uses at most 150 pm-retired-{n} rules
Recipients per receipt rule500, not adjustableSES quotas, read 2026-10-09When a pm-retired-{n} rule is full, the domain monitor opens the next one
Retired addresses bounced per deployment75,000 (150 rules × 500)Pylota MailBeyond 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 sends1 per second per account and region; not adjustableSES quotas (SES API sending quotas), read 2026-10-09One 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 sandbox200 messages per 24 hours, 1 per second, to verified addresses onlySES quotas, read 2026-10-09pmail setup ses stops until production access is enabled
Raw message in S3, and notifications in the SQS backstop14 daysPylota Mail (pmail setup ses)An object deleted before ingestion is lost: its queued ledger row becomes lost and the ses_object_lost alert pages

SMTP relay

Applies to domains connected with smtp_relay.

LimitValueSource
Ports465 (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 sentCloudflare: “Workers cannot create outbound connections on port 25” (TCP sockets, read 2026-10-09); Pylota Mail
SMTP sends in parallel4 per outbound consumer invocationCloudflare allows each invocation up to six connections waiting at once, and opening a socket counts (Workers limits, read 2026-10-09)
Connections per message1, without pipeliningPylota Mail
Timeouts10 s to connect, 30 s per command, 60 s for the reply after the final dotPylota Mail. No reply after the final dot makes the send uncertain; it is never resent
Alignment probeBefore 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_timeoutPylota Mail

API

LimitValue
Request body7 MiB (a 5 MiB message after base64 decoding, plus JSON)
Requests per API key600 per minute
Search per key120 per minute
Agentic search per key20 per minute. Tenant daily cap 500 by default
Sends per identity120 per minute. Daily caps from policy
Signing per identity (RL_SIGN): agent assertions and signed HTTP requests together600 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 partnermax_tenants tenants that are not erased: 25 by default, set by the operator (403 partner_tenant_limit)
Rate-limit headersEvery 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 size25 by default, 100 maximum
Search limit10 by default, 50 maximum
Search response size256 KB. Above it, results are cut and truncated: true
Tenant search fan-out100 identities
wait timeout60 seconds
Idempotency key retention30 days
Cursor lifetime24 hours
Metadata on identities and messages16 keys, 512 bytes per value
Labels64 per message, 64 characters each

Agent signing keys

From Agent signing keys and signed requests. Every value outside its range gets 400 invalid_request.

LimitValue
Active signing keys per identity1, plus retiring keys during an overlap
Overlap after an identity key rotationPM_IDENTITY_KEY_OVERLAP_DAYS, default 7 days
Identity JWKS cacheCache-Control: public, max-age=300: verifiers should cache it for at most 5 minutes
Assertion audience1–256 printable ASCII characters, required
Assertion expires_in60–600 seconds, default 300
Assertion nonce1–128 printable ASCII characters
Assertion ext2 KB as JSON; it cannot set a registered or Pylota claim
HTTP signature urlhttps only, 2,048 characters
HTTP signature expires_in30–300 seconds, default 60
HTTP signature componentsAlways @authority, signature-agent and from; optionally @method, @path and @query. ASCII values only
Web Bot Auth key directoryAt most 3 keys (one active, two retiring); a rotated deployment key stays listed for 7 days. Cache-Control: max-age=86400

Notifications

From Notifications and usage alerts.

LimitValue
Notification email per person50 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 workspace200 a day, all kinds except account and digest
new_mail, instantA 2-minute hold after the first message, then at most one email per person and inbox every 10 minutes
new_mail, hourly and dailyOne email at the top of each hour that had messages; one at 09:00 local time
needs_reply filterWaits up to 5 minutes for triage
“Needs a person” emailDaily at 09:00 in the workspace’s time zone
Usage alerts80% 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 link90 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 refusedHourly, for 24 hours

Storage

LimitValueSource
Durable Object SQLite per identity10 GBCloudflare. Alert at 70%. Raw MIME and attachments live in R2, so this is mostly text and index
D1 database10 GBCloudflare. Control plane only. Event and delivery logs are pruned after the tenant’s retention.events_days (default 30)
Vectorize vectors per index20,000,000Cloudflare. About 4,000–10,000 vectors per 1,000 messages
Vectorize namespaces per index50,000Cloudflare. One per tenant, so at most 50,000 tenants per index
Queue message128 KBCloudflare. Queues carry pointers only
Queue delay per retry24 hoursCloudflare
Queue retention14 daysCloudflare. Dead-letter items are kept at most 14 days

Plans

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).

Console

LimitValue
Sign-in link or code requests3 per 10 minutes per address
Code verification attempts10 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 lifetime10 minutes, single use
Two-step verification codes5 attempts a minute per person. 10 failures in a row lock two-step sign-in for 15 minutes
Recovery codes10 per person, each single use. Generating new ones invalidates the old
Google or GitHub sign-in10 minutes from start to callback, single use
WaitlistAn 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
Session lifetime7 days rolling, 30 days absolute
Re-authentication for sensitive actionssigned in within the last 10 minutes
Invitation lifetime7 days

Webhooks

LimitValue
Endpoints per tenant20
Endpoints per partner20
Platform endpoints20
Timeout per attempt15 seconds
Retry windowAbout 72 hours, 13 attempts
Replay window30 days from the event’s occurred_at (never from when the delivery went dead), or the tenant’s retention.events_days if that is shorter
Response body read4 KB