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

Plans and billing

There are two ways to run Pylota Mail. You can host it yourself on your own Cloudflare account, free and with no plan limits. Or you can use Pylota Mail Cloud, Pylota’s hosted deployment of the same code, on the Free, Developer or Team plan. This guide covers both: what each plan includes, what counts against it, what happens at a limit, who is told by email before it, how to change plans, and how an agent reads its own limits.

Self-hosting

Self-hosting is free under FSL-1.1-ALv2. You pay only your own Cloudflare usage (Deploy to Cloudflare).

  • Billing is off by default (PM_BILLING=off). You need no Stripe account, and pmail setup never asks for one.
  • There are no plan limits. Nothing returns 402 billing_limit.
  • You can still set quotas in each tenant’s policy: daily send caps per identity and per tenant (identity_daily_send_cap, tenant_daily_send_cap) and the daily agentic-search cap (search.agentic_daily_cap). They return 429 daily_cap_reached or 429 agentic_budget_exhausted (Configuration › Tenant policy).
  • GET /v1/usage still reports what each workspace uses, with "billing": "disabled" and no plan limits (every feature granted: null, unlimited: true).
  • No usage alerts are sent: with no plan there is no limit to reach. The identity and tenant daily send caps above still emit quota.warning to webhooks at 80% and 100%; the agentic-search cap emits none, only its 429.

Turning billing on (PM_BILLING=stripe, with a plan catalog and Stripe keys) is an operator choice, described in the billing design. FSL-1.1-ALv2 does not permit offering the software to others as a competing commercial product or service, so read PRD §12 before you charge anyone for a deployment.

Pylota Mail Cloud

Pylota Mail Cloud is the same Worker, run by Pylota, with billing on. Each workspace has a plan, and the plan’s allowances are enforced exactly.

Pylota Mail Cloud opens with the v1.0 release. Until then there is nothing to sign up for, and this page describes how the plans will work. Releases are announced in the GitHub repository.

Plans

FreeDeveloperTeamSelf-host
Price (GBP, excl. VAT)£0£10 a month£49.50 a month£0 under FSL-1.1-ALv2
Inboxes (identities)510100no plan limits
Sends per month1,00010,000100,000
Triage analyses per month50010,000100,000
Custom domainsnone550
Storage1 GB10 GB100 GB
Seats1210
Top-upsnone£1 per unit£1 per unit
SupportGitHub issuesemailpriority emailcontracts available

Every plan includes the full API, the MCP server, the CLI, the console, quarantine review and all four search modes. The per-identity send limits (Sending › Caps) stay on every plan as an abuse backstop.

New workspaces on Free can send at most 50 messages a day for their first 7 days (429 daily_cap_reached above that). From day 7 a daily check lifts the ramp once bounce and complaint rates are under the automatic-pause thresholds; until then the limit stays. Moving to a paid plan lifts it at once, for good.

Agentic search is not a plan allowance. It is rate-limited per key (20 a minute) and capped per workspace per day (search.agentic_daily_cap, 500 by default).

What counts

AllowanceWhat one unit isKind
InboxesOne identity, active or paused. Its addresses are freeCount
SendsOne recipient. A message to three recipients uses three sendsMonthly
Triage analysesOne stored analysis of an inbound messageMonthly
Custom domainsOne domain of your own, whatever its connection method. The shared platform domain is freeCount
StorageStored mail and attachments, in GB, rounded up and measured hourlyMeasured
SeatsOne member of the workspace, or one pending invitationCount

Only work that happened counts:

  • A send is counted when the transport accepts it. A rejected, failed or cancelled send costs nothing, and neither does a recipient skipped because of a suppression.
  • An uncertain send (Sending › Uncertain sends) releases what it held. It is counted later only if reconciliation shows it went out, or if a person resolves it as sent.
  • A triage analysis is counted only when it is stored. A failed analysis is refunded. Quarantined mail is triaged, and counted, only when someone releases it.
  • Inbound mail itself is never counted against a plan.

Each allowance is reserved before the action runs, in one place per workspace. Two requests can never both take the last unit: one succeeds and the other gets 402 billing_limit.

Top-ups

A top-up unit is one inbox, 1,000 sends or 1,000 triage analyses. It costs £1 and is added to your plan each month while it is subscribed. Top-ups are available on Developer and Team. Custom domains, storage and seats have no top-up: they come with the plan.

For example, Developer with two send top-ups has 12,000 sends a month. A top-up counts from the moment Stripe confirms it, in the current month.

When allowances reset

AllowanceResets
Sends, triage analysesAt the start of each billing period
Inboxes, custom domains, seats, storageNever: they are counts of what exists now

On a paid plan, the billing period is your Stripe subscription’s: it starts on the day you subscribed and renews monthly. On Free, periods are calendar months, starting at 00:00 UTC on the 1st. Starting or ending a subscription starts a new period, so monthly counts begin again at zero.

GET /v1/usage gives each allowance’s exact resets_at.

What happens at a limit

When an allowance is spent, the action that needs it is refused before anything is stored:

{
  "error": {
    "code": "billing_limit",
    "message": "This workspace has used its sends for this billing period.",
    "retryable": false,
    "fix": "Upgrade the plan or add a top-up, then retry with the same Idempotency-Key.",
    "request_id": "req_01JA2Q7M…",
    "details": { "feature": "sends", "granted": 12000, "used": 12000,
                 "resets_at": "2026-11-01T00:00:00Z",
                 "upgrade_url": "https://mail.example.com/console/plan" }
  }
}
  • Safe to retry after an upgrade. The 402 is returned before any idempotency record is written. When the workspace upgrades or adds a top-up, the same request with the same Idempotency-Key succeeds, and the email is sent once (Sending › Safe retries).
  • Not retryable as it is. retryable is false: retrying without a change gets the same answer. An agent should stop, tell a person, and keep the key and the body for later.
  • Replays still work. Retrying a send that already succeeded returns its original result, with deduplicated: true, even when the allowance is now spent.
  • All or nothing. A send to three recipients with two sends left is refused whole. Nothing is sent.
  • The first refusal of each allowance in a billing period emits a billing.limit_reached event.

Inbound mail is never refused because of a plan. When the triage allowance is spent, mail is still stored and delivered to your webhooks; triage is skipped with reason allowance (the deterministic risk flags are still set), and you can re-run it after a top-up (Triage).

Storage over its allowance blocks only new identities, new domains and outbound messages with attachments, each with 402 billing_limit and feature: "storage_gb". Mail keeps arriving, and sends without attachments keep working. Delete or erase mail, shorten retention, or upgrade to bring it back under the limit.

Usage alerts

People in the workspace get an email when an allowance reaches 80% and 100% of its limit (granted, top-ups included). These emails are for the people behind the agents; agents keep reading limits from the API and webhooks (Notifications design).

  • Who gets them. The owner and admins, by default. Members and viewers get none unless they turn them on. Each person turns them on or off for themselves at Settings › Notifications (/console/settings/notifications), or with the one-click unsubscribe link in the email (Receiving › Notifications by email).
  • How often. For allowances that reset (sends, triage analyses), each threshold alerts at most once per billing period, even if usage drops back and crosses it again. For counts that do not reset (inboxes, custom domains, seats, storage), an alert goes out when the count crosses a threshold upwards, then not again for 24 hours for that allowance and threshold.
  • What the email says. The allowance in plain words, used and granted, when it resets (or that it does not), and what happens at 100%, for example “sends return 402 billing_limit until 1 November”. It links to Plan and usage, and for the owner to buying a top-up. The subject reads like [Pylota Mail] Sends at 80% for Brightwell.
  • Webhooks are unchanged. quota.warning and billing.limit_reached events still go to your endpoints, so agents and your backend learn about limits the same way as before.
  • Self-hosted with billing off. There are no plan limits, so no usage alert is sent.

Upgrade, downgrade and cancel

Plans are managed on the console’s Plan and usage page (/console/plan). Every member can see it. Only the workspace owner can change the plan, and the console asks the owner to confirm with a code if they last signed in more than 10 minutes ago.

  • Upgrade from Free. Choose Developer or Team. The console sends you to a Stripe Checkout page to pay. The plan applies as soon as Stripe confirms the payment, usually within seconds, and a new billing period starts.
  • Add top-ups. On Developer or Team, choose how many units of inboxes, sends or triage analyses to add. The first purchase of each kind goes through Checkout.
  • Change plan, change top-ups, update the card, see invoices, cancel. Manage billing opens the Stripe Customer Portal. A change applies when Stripe confirms it; Stripe prorates the price.
  • Cancel. The plan stays until the end of the period you paid for, then the workspace moves to Free.
  • Delete the workspace. Deleting a workspace (owner only, at Settings) stops its mail, then cancels its plan and every top-up at once, with no proration and no refund, before anything else is erased (Privacy).

A downgrade never deletes data. If you have more identities, custom domains or members than the new plan allows, all of them are kept and keep working: identities still send and receive, domains still send, members can still sign in. Creating more is refused with 402 billing_limit until the counts fit the new plan. If you have already used more sends or triage analyses this period than the new plan allows, those are refused until the next reset.

Pending invitations count as seats. To free seats, revoke invitations or remove members on the Members page.

Failed payments

If a renewal payment fails, the workspace keeps its plan for a 7-day grace period (PM_BILLING_GRACE_DAYS):

  1. A billing.payment_failed event is sent, with grace_until, and the console shows a banner.
  2. Stripe retries the payment. The owner can also pay or change the card in the Customer Portal.
  3. If the payment succeeds within the grace period, nothing else happens.
  4. If not, the workspace moves to Free limits and a billing.plan_changed event is sent with reason payment_failed_grace_ended. Nothing is deleted: counts above Free’s limits behave as after a downgrade.

Paying later restores the plan, with a billing.plan_changed event whose reason is payment_recovered.

Reading usage

Agents can read their own limits before they hit one (REST API › Usage).

GET /v1/usage works with every tenant and identity key for its own workspace: they hold usage:read there implicitly. A platform or partner key needs usage:read and must pass tenant_id (a request without tenant_id gets 400 invalid_request).

curl https://mail.example.com/v1/usage -H "Authorization: Bearer $PYLOTA_MAIL_KEY"
{
  "billing": "metered",
  "plan": { "plan_id": "developer", "status": "active", "current_period_end": "2026-11-01T00:00:00Z",
            "cancel_at_period_end": false },
  "features": [
    { "feature": "inboxes",        "granted": 10,    "used": 4,    "remaining": 6,    "unlimited": false, "resets_at": null },
    { "feature": "sends",          "granted": 12000, "used": 8312, "remaining": 3688, "unlimited": false, "resets_at": "2026-11-01T00:00:00Z" },
    { "feature": "triage",         "granted": 10000, "used": 2210, "remaining": 7790, "unlimited": false, "resets_at": "2026-11-01T00:00:00Z" },
    { "feature": "custom_domains", "granted": 5,     "used": 1,    "remaining": 4,    "unlimited": false, "resets_at": null },
    { "feature": "storage_gb",     "granted": 10,    "used": 2,    "remaining": 8,    "unlimited": false, "resets_at": null },
    { "feature": "seats",          "granted": 2,     "used": 2,    "remaining": 0,    "unlimited": false, "resets_at": null }
  ],
  "topups": { "inboxes": 0, "sends": 2, "triage": 0 },
  "plans": [ { "plan_id": "free", "name": "Free", "price": 0, "currency": "gbp", "interval": "month",
               "included": { "inboxes": 5, "sends": 1000, "triage": 500, "custom_domains": 0, "storage_gb": 1, "seats": 1 },
               "topups": false, "support": "github_issues" } ]
}
  • billing is metered, exempt (no limits) or disabled (self-hosted without billing).
  • granted includes top-ups. remaining also allows for actions in flight, so it is what you can use now.
  • plans is the full plan catalog.

GET /v1/usage/daily (usage:read, tenant, partner or platform key) gives per-day figures: inbound, outbound, sends, triage, search, agentic searches, AI usage, storage, and the agent assertions and signed HTTP requests minted (assertions, http_signatures), for up to 92 days per request. Signing is counted but not limited by any plan.

GET /v1/plans needs no key. It returns the plan catalog, or { "billing_enabled": false, "data": [] } on a deployment without billing.

CLI. pmail usage prints the allowances table, and pmail usage daily the per-day figures. Add --json for the raw response.

MCP. mail_get_usage returns the same object. It is read-only and takes no input; every tenant and identity key sees it for its own workspace, and platform and partner keys do not (they use REST with tenant_id). A billing_limit tool error also carries the feature, the numbers and resets_at in its details.

Tax

Prices are in pounds sterling (GBP) and exclude VAT. Every customer is billed in GBP; there are no local-currency prices in v1.0. Stripe Tax adds UK VAT, and VAT or sales tax in other countries, where it is due, based on the billing address you enter on the Checkout page. A business can add its VAT or other tax ID there or in the Customer Portal, and it appears on invoices.

Support

PlanSupport
FreeGitHub issues
DeveloperEmail
TeamPriority email
Self-hostSupport contracts are available

Security problems go to the process in SECURITY.md, never to a public issue.