Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,43 @@ AGENT_TOOL_TOKEN=
# Do not accept a default in production.
WORKER_SHARED_SECRET=

# The deployment's mailbox, which a Bot reads and answers through the Mailbox connector. All three
# are needed together: set one and the server refuses to start naming the others, rather than booting
# with half a mailbox that fails at the first login in front of somebody. Leave all three unset and
# the connector is still there, still grantable, and every call answers with what to set.
#
# MAILBOX_USERS is a comma-separated list of addresses, all on the hosts above, which is the shape a
# shared host gives you: support@, sales@ and billing@ are three mailboxes on one IMAP and one SMTP
# server. The FIRST is the default, the one a tool call that named no account works in, so the order
# is a decision rather than a detail. One address is a perfectly good list.
#
# MAILBOX_USER, the singular this feature shipped with, is still read as a list of one, so an
# existing deployment keeps working unedited. Setting both refuses to start: they are two answers to
# the same question.
#
# THE PASSWORDS ARE NOT HERE, and that is deliberate. They are the only secrets in this feature, so
# they live where this deployment's other secrets live: the encrypted vault. Store one per account
# at /admin/credentials with kind `mcp`, provider `mailbox` and key id the address itself, and
# rotate them there. A password in this file is a password in a repository, a compose file and every
# process list that ever read it.
MAILBOX_IMAP_HOST=
MAILBOX_SMTP_HOST=
MAILBOX_USERS=
# MAILBOX_USERS=support@example.com,sales@example.com
#
# Both default to the implicit-TLS ports, 993 and 465, so the connection is encrypted before the
# password is sent rather than negotiating for it in the clear. Set them only for a server that
# listens elsewhere.
# MAILBOX_IMAP_PORT=993
# MAILBOX_SMTP_PORT=465
#
# Where a Bot may send mail, as a comma-separated list of domains. Unset or empty means anywhere,
# which is the default and the behaviour every deployment had before this existed.
#
# Worth setting, because the policy engine cannot do this job: a rule sees a tool call's name and
# effect, never its arguments, so the only rule you can write about send_message covers all of it or
# none of it. Meanwhile the reading tools bring text somebody else wrote into a model's context, so
# a Bot that can read and send without limits is one persuasive message away from mailing the inbox
# to whoever asked for it. This bounds where anything can go; an approval rule on send_message in
# your boundaries decides whether it goes at all. Deployments that care should have both.
# MAILBOX_ALLOWED_RECIPIENT_DOMAINS=example.com,partner.example
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,48 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.

## Unreleased

### A Bot can read and answer the deployment's own mailbox

A new catalogue entry, Mailbox, appears at `/admin/plugins/mailbox`. It gives a Bot four tools
against the deployment's own mail: `list_messages`, `read_message`, `search_messages` and
`send_message`, the last of which threads a reply when it is given the uid of the message being
answered, and files a copy of what it sent in the account's Sent folder so webmail and the Bot both
find the outgoing mail. The mailbox belongs to the deployment rather than to whoever is asking, so
everybody granted the tools reads the same mail. Decide that before granting it, because it is the
whole of the access model.

Configure it with `MAILBOX_IMAP_HOST`, `MAILBOX_SMTP_HOST` and `MAILBOX_USERS`, which are needed
together: set one of the three and the server refuses to start naming the others, rather than
booting with half a mailbox that fails at the first login in front of somebody. `MAILBOX_USERS` is
a comma-separated list, so several accounts on one shared host are one deployment, and the first
listed is the default that a call naming no `account` works in. `MAILBOX_USER` is still read as a
list of one for a deployment that already had it, and setting both refuses to start.
`MAILBOX_IMAP_PORT` and `MAILBOX_SMTP_PORT` default to the implicit-TLS ports, 993 and 465. Leave
the three required variables unset and the connector is still listed and still grantable, and every
call answers with the sentence naming what to set.

The passwords are not environment variables. Each account's password is a row in the encrypted
credential vault at `/admin/credentials`, kind `mcp`, provider `mailbox`, key id the address itself,
so a deployment with three mailboxes holds three rows and rotates or revokes each on its own. A
password is read at the moment a call needs it, so a rotation takes effect on the next tool call
rather than on the next restart, and a revocation stops that account within a call. Nothing prints
one: IMAP command logging is off, and a mail server's failure sentence is scrubbed of the plaintext
and of the base64 forms before it reaches an audit row, a transcript or a model.

`MAILBOX_ALLOWED_RECIPIENT_DOMAINS` bounds where mail may go, refusing a recipient outside the list
before any connection is opened. Unset means anywhere, which is the behaviour every deployment had
before this existed. It is there because a policy rule cannot do this job: a rule sees a tool call's
name and its effect, never its arguments, so the only thing it can express about `send_message` is
whether it happens at all. An approval rule decides that mail goes; the allowlist decides where it
may go. A deployment that cares should set both.

Grants are per tool and not per account, so "may read the mail" and "may answer it" are two separate
decisions, while "may read support@ but not billing@" is not one this deployment can express: if an
account must stay out of a Bot's reach, do not configure it here. Every call takes the same route as
any other connector's. The Bot's grant is checked, the policy is evaluated with the tool's effect
(`send_message` as a write, the other three as reads), and an audit row is written, before any mail
server is dialled.

### Coworkers are made in a wizard and managed in a dialog

Creating a coworker is now a three-step wizard — who it is, who may see it, then where it runs,
Expand Down
Loading