Email-as-a-service for agents (receive, store, and send agent email per node). The out-of-process ryu-mail sidecar; Core proxies /api/mail/* to it.
The public home of
ryu-mail. Source, builds, and releases live here — binaries for every platform are attached to each release.This tree is generated from the Ryu monorepo, so commits pushed here directly are replaced on the next sync. Pull requests are welcome — open them here and they are ported into the monorepo, then flow back out. Ryu as a whole: https://github.com/amajorai/ryu
App: Install (opens the Ryu desktop app and asks you to confirm)
CLI:
ryu apps add @ryu/mailCrate:
cargo install ryu-mailPrebuilt binaries for every platform are attached to each release.
Apache-2.0 — see LICENSE.
backend/—ryu-mail(out-of-process sidecar). A standalone Axum binary (its own crate, ZERO dependency onapps/core) that ownsmail.db: inbox registry, message store, MIME assembly, and SMTP send (lettre) + inbound parse (mail-parser). Core spawns it (SidecarProcess::Local, host/sibling binary, no download), health-checks/health, and proxies/api/mail/*to it on loopback. Route paths are byte-identical to Core's old in-process routes so the proxy passes straight through.ui/— companion (@ryu/mail-app). A sandboxed full-page Companion (Path B,ui_format: "html"), built to one self-containeddist/index.htmlviavite-plugin-singlefile. A per-node mail client: list inboxes, read messages, compose/send, create an inbox (revealing its inbound webhook secret + forwarder URL). Every call goes over thewindow.ryubridge, neverfetch.
- Capability grant:
mail:crud— the bridge capability the companion calls. - Sidecar:
ryu-mailon:7996,command_env: RYU_MAIL_BIN,port_env: RYU_MAIL_PORT,http.public_mount: /api/mail(a built-in owning a stable external URL),max_body_bytes ~26MBfor attachments. - Runnable: one
companion(Agent Inboxes, iconmail-01). - App events (
contributes.hook_events):@ryu/mail#message.receivedand@ryu/mail#message.sent, raised by the sidecar throughevents.emitso a plugin hook or workflow can react to mail without either side knowing the other exists. Both carry metadata only (ids, addresses, subject, timestamps) — the fan-out payload reaches every subscriber verbatim, so bodies stay behind the authedGET /api/mail/messages/:id.message.sentmarks SMTP hand-off, not delivery.
The same ryu-mail binary can serve one independently deployed Mail tenant. Set
an explicit standalone token and bind address; the standalone token is separate
from Core's injected RYU_EXT_TOKEN:
RYU_MAIL_MODE=standalone \
RYU_MAIL_API_TOKEN=mail-example-secret \
RYU_MAIL_HOSTNAME=0.0.0.0 \
RYU_MAIL_PORT=7996 \
RYU_DIR=/var/lib/ryu-mail \
cargo run --manifest-path apps-store/mail/backend/Cargo.tomlThe existing /api/mail/* API is available directly. Put TLS, rate limiting,
and the inbound mail provider in front of the service. Standalone mode is
single-tenant: the configured bearer can read and manage every inbox in that
service's database. Incoming messages still require the per-inbox HMAC secret;
they do not use the bearer token.
The standalone root GET / redirects people to the Ryu organization dashboard;
the /api/mail/* API and inbound webhook remain available for service callers.
RYU_MAIL_API_TOKEN must be present before a non-loopback bind is allowed. A
Core-issued token is accepted only for the loopback sidecar path and is never a
standalone credential.
Core-hosted mode binds loopback only and fail-closes: protected /api/mail/*
routes require the shared-secret bearer Core injects at spawn. Standalone mode
uses RYU_MAIL_API_TOKEN with the same protected routes. The inbound webhook
(POST /api/mail/inbound/:id) keeps its own per-inbox HMAC-SHA256 auth and is
reachable tokenless.
Mail scales and fails independently of the node. Because the backend is a separate
process behind a stable /api/mail/* contract, any equivalent mail backend can
replace ryu-mail without touching Core.