Security model

Security

Keelpost is a mailbox that hands you decisions instead of messages, in private development, with no production users. This page describes how the system is built today and what it deliberately refuses to do. It is not a compliance page, and the last section says plainly what we do not claim.

One mailbox, one store

Each mailbox is its own SQLite-backed Durable Object. Messages, threads and mailbox settings live in that mailbox’s own database, and attachment blobs live in object storage belonging to the deployment. There is no shared table holding every workspace’s mail side by side, because there is no shared mail table at all.

The shared control-plane database holds accounts, workspaces, mailbox ownership and grants. It does not hold your message bodies.

Every request that touches mail names a mailbox. The server checks your grant on that mailbox, then opens that mailbox and no other. Cross-mailbox reads are not a permission we forgot to check; there is no code path that opens two mailboxes to answer one question.

Roles and grants

There are four roles: owner, admin, operator and viewer. Owners and admins can see the mailboxes belonging to their workspace. Operators and viewers see nothing by default and must be granted access to a specific mailbox.

Membership is invitation-only. There is no self-serve join, no “anyone with an address at this domain” rule, and no open sign-up anywhere in the product.

Revocation is the part most systems get wrong. Removing a grant is not a flag that takes effect at the next login. The next action from that session — a folder open, a search, an agent turn, a tool call — checks the live grant and fails. A user who is looking at a mailbox when their access is removed cannot keep using it.

The agent inherits this exactly. It runs as you, with your grants, re-checked on every turn and every tool call. It has no ambient service identity that could read a mailbox you cannot.

Approval before every state change

The agent can read, search and summarise on its own. Six of its ten tools change something — drafting a message, drafting a reply, revising a draft, marking read, moving a message and discarding a draft — and every one of those stops at a card.

The rule is written as a default rather than a list: a tool runs without a card only if it is a known read tool, so a tool added tomorrow fails safe to one. The card is server-rendered from the stored record: the actual message, and the actual change that will be applied. It is not the model’s account of what it is about to do, which means a confused or manipulated agent still cannot show you a card that misdescribes the action.

Approving is what executes the action, and the permission check runs again at that moment. Denying does nothing at all.

The assistant that drafts replies to incoming mail is narrower still: it has one capability, drafting a reply. There is no send in that path, so no failure of judgement in it can deliver a message.

There is no configuration option, mailbox record, per-action setting or per-sender allow-list that disables approval. The same boundary applies when an MCP client requests the change.

Illustration. The field names, the two-block layout and the revision check match the product; the message shown is fabricated sample data.

Where this is going, and why it is a loosening

Everything above is present tense and shipped. This paragraph is not, and it is here because the change it describes will look like a weakening if it arrives unannounced.

Today every state change asks, reversible or not — filing a newsletter asks exactly as loudly as deleting a message. That is not as safe as it sounds. The literature on this is blunt and the failure mode is well named: an agent that asks about everything trains a person to stop reading, and an approval clicked through without being read has bypassed the oversight it was supposed to provide. The design we are building toward sorts actions by whether they can be undone: the reversible ones act and report, with an undo on each; the ones that cannot be taken back — sending and deleting — always stop, always name the exact action on the button, and can never be swept into an “approve all”.

Two commitments attach to that, and they are checkable against this page later. Undo has to exist before the reversible tier stops asking, so nothing becomes automatic before it becomes recoverable — and there is no undo in the product today. And a send, when sending is verified, will leave on a short delay with a visible cancel: approving it sends it rather than leaving a second step, while the mistake stays recoverable for as long as the delay lasts.

Prompt-injection scanning

Inbound mail is hostile input. Anyone can write to your address, and anyone writing to your address can try to write instructions to your agent.

Before the drafting model sees a message, the message is scanned for prompt injection. If the scan flags it, automatic drafting for that message is blocked. If the scan itself errors, the message is treated as if it had been flagged — the system fails closed rather than fails open. The exact text handed to the scanner is the same text handed to the drafting model, capped at a fixed byte budget, so nothing larger or different slips past the check downstream.

Know exactly what the scan gates: automatic drafting, and only that. A flagged message is not quarantined. It stays in your mailbox and stays readable, so if you ask the agent to read or summarise it, or an MCP client does, its content reaches the model like any other message. The scan removes the case where hostile mail steers the agent with nobody in the room; it does not remove the case where you point the agent at hostile mail yourself.

This reduces a class of attack. It does not eliminate it. Treat agent output the way you treat any other summary of an untrusted document.

You keep the parts that matter

You bring the domain. You bring the Cloudflare account and the routing configuration. You bring the keys. Keelpost is the client on top of infrastructure you already control, which means the strongest revocation available to you is one you perform yourself: change the routing and mail stops arriving here, without asking us.

Be precise about where that boundary sits. What is yours is the domain, the DNS records, the Email Routing rules and the credentials. What is ours is where the mail then lands: mailbox storage, attachment storage and model inference all sit inside the Cloudflare account boundary of the deployment, not inside yours.

The agent runs on Cloudflare Workers AI inside that same account boundary as the rest of the deployment. Message content goes to that model when you use the agent or when auto-drafting runs. There is no other model provider in the path, and no third-party analytics, script, font or image origin on this website.

The MCP endpoint

Keelpost exposes 14 tools over OAuth 2.1 with PKCE: list_mailboxes, list_emails, get_email, get_thread, search_emails, draft_reply, create_draft, update_draft, discard_draft, delete_email, send_reply, send_email, mark_email_read, move_email.

Each is gated by scope — mailboxes:read, mail:read, mail:draft, mail:manage, mail:send, mail:delete — and on top of the scope, a token still cannot exceed the mailbox grants of the account that authorised it. Clients are created and managed by an owner or admin, who can disable, rotate or delete them and revoke consent. Dynamic client registration is deliberately turned off, so no client can register itself.

Be clear about the difference from the in-app agent, because it does not run the way you would expect. An MCP client is stopped by scope, and then stopped again. The seven tools that would change something — send_email, send_reply, delete_email, discard_draft, update_draft, move_email and mark_email_read — do not run when they are called. A scope proves which client is calling; it does not prove a person saw the change. So the call comes back with a link instead, and the link opens the same approval card the in-app agent uses, on a page only you can sign into. The client repeats the call afterwards, and the change happens then — once, for exactly the change you read.

Three things about that are worth saying plainly, because they are what a weaker version of this would get wrong. A client telling us you approved is not approval; the only thing that counts is the record your browser wrote. The approval is bound to that one change, so approving a move to Archive does not let a move to Trash through. And it is bound to you: a link opened by a colleague, however senior, shows them nothing.

The seven remaining tools work unchanged: listing mailboxes, listing messages, reading a message, reading a thread, searching, drafting a reply and creating a draft. A draft is inert — you review it and send it from the app. No mailbox setting bypasses approval. Grant the smallest scope set that does the job regardless; the default set for a managed client is read-only.

What we do not claim

We have no certifications, and we are not pursuing a badge to put on this page today. No SOC 2 Type I or Type II. No ISO 27001. No HIPAA, no PCI, no FedRAMP. No third-party audit, no penetration test report, no bug bounty programme.

We have no production users, no uptime record and no SLA. Nothing in this product has been operated at scale, because it has not been operated at all outside development.

Sending is not verified. The outbound path is built to persist a message before publishing any job, and to retry with dead-letter queues, but it has not been proven against a live provider, and until it has we will not describe it as working.

The briefing does not exist. It is what this product is for, it is drawn on its own page, and there is no code behind any of those drawings. Neither Google Workspace nor Microsoft 365 can be connected, there is no undo anywhere, and no price has been set.

We have no customers, no case studies and no reviews to point you at. If you find any of those on a page claiming to be us, it is not us.

If a specific control matters for your decision, ask before you assume. We would rather answer “not built” than let you infer that it is. The rest of the ledger — what is built and what is not — is on the roadmap.

Join the waitlist

If this is the shape of thing you want between an agent and your mail, leave an address. Sign-ups are closed while we build; the list gets one email, when they open.

We store the address you type, plus a hashed form of your IP address so the form can be rate-limited. One email when sign-ups open, and nothing after that. We do not share the list, and there is no analytics or third-party script on this site.

Also worth reading: what is built and what is not, and the FAQ.