Fair Source FSL-1.1-ALv2 · Rust on Cloudflare Workers

Email and identity for AI agents.

Written in Rust. Self-host it free on your own Cloudflare account, or use Pylota Mail Cloud.

cargo install pylota-mail-cli --locked && pmail setup

Message headers

Runs-On:
your Cloudflare account
Written-In:
Rust, compiled to WebAssembly
Speaks:
REST API, MCP, CLI, signed webhooks
Licence:
FSL-1.1-ALv2, Apache-2.0 after two years

Authentication-Results: spf=pass dkim=pass dmarc=pass

Capabilities

A real mailbox for every agent, with the checks built in.

  • An identity per agent

    Each agent gets its own identity and address. Move the address to another domain and its history moves with it.

  • Idempotent sending

    Send, reply and forward all require an idempotency key, so a retried request returns the first result.

  • Inbound you can trust

    Every inbound message is parsed, authenticated with DKIM, ARC and DMARC, and placed in its thread.

  • Four ways in

    Signed webhooks tell your agent what happened. A REST API, an MCP server and a CLI let it act.

Why Pylota Mail

The guarantees other agent mail leaves to you.

Eight promises, each enforced in code and covered by named tests you can read.

  1. Answers you can check

    Agentic search answers with message IDs, and code, not the model, checks every citation. A sentence the evidence does not support is removed and recorded.

    every citation verified · removals in the trace

  2. One email per intent

    Every send needs an Idempotency-Key. A retry returns the first result. An outcome the transport cannot confirm is marked uncertain and never resent.

    status: "uncertain" → never resent

  3. Identities outlive domains

    Move an agent from the shared domain to your own and back. History stays, threads keep the address the other side used, and old addresses bounce cleanly.

    promote · retire · roll back · 550 5.1.6

  4. Never sends mail that fails checks

    Domains are checked every 15 minutes from two resolvers. When a record breaks, sending moves to an aligned fallback in the same thread, and you get the exact fix.

    two resolvers · two checks · aligned fallback

  5. Built for untrusted input

    Every message carries SPF, DKIM and DMARC verdicts and trust flags. Hidden text is stripped, models see fenced content, and risky attachments wait for a person.

    trust.flags · quarantine · human release

  6. Your account, your receipts

    Mail stays in your Cloudflare account, in the EU if you choose. Erasure deletes from every store and returns a receipt with probe queries that come back empty.

    receipt.probe: { keyword: 0, semantic: 0 }

  7. Tested against the edge cases

    A public register of 201 edge cases, from SMTPUTF8 addresses to zip bombs, DNS takeovers, lost SES notifications and billing races, each mapped to a named test. Search quality is gated in CI.

    201 edge cases · 201 named tests

  8. Nothing to keep running

    One Rust Worker compiled to WebAssembly, on Cloudflare primitives. No servers, no external database, and no always-on compute to pay for while it is idle.

    one Worker · no servers · no database

A message's life

Receive, understand, then act.

Every message passes through the same three stages, in this order.

  1. Receive

    A catch-all route sends mail for every address on your domain to the right identity. Messages up to 25 MiB are accepted, parsed and given a sender verdict.

    Mail that fails authentication, looks like spam or carries an unsafe attachment goes to quarantine. A person releases it. An agent cannot.

    • Catch-all routing to identities
    • Inbound messages up to 25 MiB
    • DKIM, ARC and DMARC sender verdicts
    • Quarantine with human release
  2. Understand

    Triage runs on every message as it lands: a category, a needs-reply score, an urgency from 0 to 3, and risk flags such as a payment-change request or a suspected prompt injection.

    Your agent reads the triage first and decides what to open. When it needs history, search finds it.

  3. Act safely

    Every send needs an Idempotency-Key. If your agent retries with the same key, it gets the original result back instead of a second email.

    A send whose outcome is uncertain is never resent automatically. Webhooks to your agent retry with backoff for up to 72 hours.

    pmail
    $ pmail send --identity bookings@acme.example.com \
        --to renter@example.org --subject "Your booking BK-2291" \
        --text "Your car is ready at 9:00." \
        --idempotency-key bk-2291-confirm
    { "id": "msg_01JA5C2H8R", "status": "queued", "deduplicated": false }
    
    # the connection dropped before the reply arrived, so the agent retries
    $ pmail send --identity bookings@acme.example.com \
        --to renter@example.org --subject "Your booking BK-2291" \
        --text "Your car is ready at 9:00." \
        --idempotency-key bk-2291-confirm
    { "id": "msg_01JA5C2H8R", "status": "queued", "deduplicated": true }
    Same key, same message ID. One email sent.

Limits

Inbound message
25 MiB
Outbound message
5 MiB
larger files go out as expiring links
Recipients per message
50
Webhook retries
up to 72 h

Developers

Connect your agent the way it already works.

  • MCP server

    Point any MCP client at /mcp with a scoped key.

  • CLI

    pmail sets up the deployment, manages identities and runs searches.

  • REST API

    JSON over HTTPS. Every write that sends mail takes an Idempotency-Key.

  • Rust SDK

    A typed client for services written in Rust.

  • Signed webhooks

    Events pushed to your endpoint, signed, and retried for up to 72 hours.

MCP

mcp.json
{
  "mcpServers": {
    "pylota-mail": {
      "url": "https://mail.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${PYLOTA_MAIL_KEY}"
      }
    }
  }
}

CLI

shell
# give the bookings agent its own identity
pmail identities create --username bookings --display-name "Acme Car Hire"

# exact reference lookup, filtered by sender and attachment
pmail search "from:@brightwell.example ref:AB12CDE has:attachment" --identity bookings@acme.example.com

# ask a question and get an answer with cited message IDs
pmail ask "Did the insurer accept the Golf claim?" --identity compliance@acme.example.com

curl

shell
curl -X POST https://mail.example.com/v1/identities/idn_01J9Z3K8V4/messages \
  -H "Authorization: Bearer $PYLOTA_MAIL_KEY" \
  -H "Idempotency-Key: bk-2291-confirm" \
  -H "Content-Type: application/json" \
  -d '{"to":["renter@example.org"],"subject":"Your booking BK-2291","text":"Your car is ready at 9:00."}'

Rust SDK

src/booking.rs
use pylota_mail::Client;

async fn confirm_booking(key: String) -> Result<(), pylota_mail::Error> {
    let client = pylota_mail::Client::new("https://mail.example.com", key);

    let sent = client
        .identity("idn_01J9Z3K8V4")
        .send()
        .to("renter@example.org")
        .subject("Your booking BK-2291")
        .text("Your car is ready at 9:00.")
        .idempotency_key("bk-2291-confirm")
        .await?;

    // a retry with the same key returns this same message
    println!("{} deduplicated={}", sent.id, sent.deduplicated);
    Ok(())
}

Security

Give each agent the narrowest key that does the job.

  • Keys are scoped to a platform, a tenant or a single identity, and a key can never create one wider than itself.
  • Keys are stored only as hashes.
  • No key can release quarantined mail without the human-review permission.
  • Every message an agent reads carries trust metadata, so it knows what came from a verified sender.

Platform

Built on Cloudflare, in Rust.

One Rust Worker, compiled to WebAssembly, runs on Cloudflare primitives in your account.

inEmail Routing
computeRust WorkerWebAssembly
  • Durable Objectsone per identity
  • D1
  • R2
  • Queues
  • Vectorize
  • Workers AI
  • EU jurisdiction. D1, Durable Objects and R2 can be pinned to the EU.
  • No text in vectors. Vectorize stores no message text.
  • Your account. Mail and keys stay in the Cloudflare account you deploy to.

Pricing

Start free. Pay as your agents grow. Or run it yourself.

Every plan includes the full API, the MCP server, the CLI, the console, quarantine review and all four search modes.

  1. Free

    Try agents with real inboxes. No card required.

    £0a month

    Get early access
    • 5 inboxes
    • 1,000 sends a month
    • 500 triage analyses a month
    • 1 GB of storage
    • 1 seat
    • Help on GitHub issues
  2. Developer

    For one person's agents in production.

    £10a month

    Get early access
    • 10 inboxes
    • 10,000 sends a month
    • 10,000 triage analyses a month
    • 5 custom domains
    • 10 GB of storage · 2 seats
    • Email support · £1 top-ups
  3. Self-host

    Your Cloudflare account, your data.

    £0under FSL-1.1-ALv2

    Deploy guide
    • API, MCP, CLI and console
    • No plan limits, billing off by default
    • One Rust Worker, no servers
    • EU jurisdiction optional
    • Support contracts available

Prices in pounds sterling, excluding VAT. A top-up is £1 a month for one more inbox, 1,000 more sends or 1,000 more triage analyses. Monthly allowances reset on your billing date; inbox, domain and seat counts do not. Pylota Mail Cloud opens with the v1.0 release.

Self-host

Read the code. Run it on your own account.

The source is published under the Functional Source License (FSL-1.1-ALv2): free to run for your own agents, and each release becomes Apache-2.0 two years after it ships. Install the CLI and pmail setup creates the Cloudflare resources it needs.

You will need

  • A Cloudflare account on the Workers Paid plan
  • One domain on Cloudflare DNS for the shared agent addresses
  • Optional: an AWS account, so tenant domains can stay at any DNS host
PILOTAAI / pylota-mail Email and identity for AI agents. FSL-1.1-ALv2RustCloudflare Workers
shell
git clone https://github.com/PILOTAAI/pylota-mail
cargo install pylota-mail-cli --locked
pmail setup --domain mail.example.com --mail-domain agents.example --jurisdiction eu
pmail deploy

Questions

Before you deploy

Is it free?

Self-hosting is free under FSL-1.1-ALv2: you pay only your own Cloudflare usage, with no plan limits. Pylota Mail Cloud has a free plan and paid plans from £10 a month; see pricing.

Is it open source?

It is Fair Source. The code is public and you can run, change and redistribute it for any purpose except offering a competing hosted service. Each release becomes Apache-2.0, a standard open-source licence, two years after it ships.

Do I need my own domain?

On Pylota Mail Cloud, no: every inbox gets an address at pylotamail.com, such as bookings.brightwell@pylotamail.com. To self-host you need one domain on Cloudflare DNS whose apex receives agent mail, for example agents.example. Your company's main domain and its mailboxes stay where they are.

Can operators bring their own domain?

Yes, wherever their DNS is hosted. A domain at any DNS host needs a few records (MX, DKIM, a bounce subdomain) and then sends and receives through Amazon SES, on the apex or a subdomain, so existing mail keeps working. Operators can also keep their own mailbox and forward to the agent, or send through their own mail provider: a daily check proves their mail still passes DMARC before the agent sends as them. Domains on Cloudflare work directly. Identities keep their history when they move. The custom domains guide walks through each option.

Where is data stored?

In your Cloudflare account. EU jurisdiction is optional.

How do agents avoid prompt injection from email?

Every message carries trust metadata, suspicious mail goes to quarantine, search tools are read-only, and your agent runtime keeps approval gates on actions. See the security guide.

Give your agents an address you control.

Deploy Pylota Mail to your own Cloudflare account and send the first message today.