π€ AgentTransfer

Every AI agent gets an email address. Upload a file, get a link, send it to any agent or any human. Open source, self-hostable.
The address comes with an API key and a folder. Files live in persistent, quota-bound folders; transfers happen over content-addressed links that expire within 24 hours; the handoff travels as ordinary email, so anything with an inbox can receive it. Every action leaves an ed25519-signed, hash-chained receipt.
One static Go binary. One data folder. Goes live from any machine with one command β or self-host everything on a $5 VPS with three DNS records.
ββββββββββββββββ upload βββββββββββββββββββββββββ email (manifest) ββββββββββββββββ
β OpenClaw β βββββββββββΊ β your AgentTransfer β ββββββββββββββββββββΊ β Codex agent β
β (agent A) β β instance β β (agent B) β
ββββββββββββββββ β β ββββββββββββββββββββ ββββββββββββββββ
β folders: persistent β HTTPS download
you, CC'd on βββββββΊ β links: β€24h, sha256 β (hash-verified)
every transfer β receipts: signed β
βββββββββββββββββββββββββ
Why
Agents increasingly need to hand artifacts to each other: model weights, datasets, build outputs, screen recordings. Across runtimes (OpenClaw β Codex β Cursor), across machines, across organizations. Today that means pasting bytes through context windows, sharing cloud-drive credentials, or standing up S3 + presigned URLs + a notification channel.
AgentTransfer's bet: email is the control plane, HTTPS is the data plane.
- Email gives you identity, addressing, notification, human visibility, and cross-instance federation for free. Any agent with an inbox can participate; no registry, no SDK.
- HTTPS moves the bytes: streamed, ranged, fast β never squeezed through email or a context window.
- Content addressing (sha256 everywhere) means every receiver can verify what it got.
- Signed receipts mean you can prove who sent what to whom β without trusting the operator.
Try it in 30 seconds
git clone https://github.com/shehryarsaroya/agenttransfer
cd agenttransfer && go build -o agenttransfer .
./agenttransfer demo
The demo spins a throwaway local server, creates two agents, hands a 1 MiB file from alice to bob (upload β send β long-poll β download), verifies the sha256 on both ends, and cryptographically verifies the signed receipt chain. No domain, no keys, no config.
Go live in one command
./agenttransfer serve --connect
# connect: registered β this instance is https://quiet-moth-79.agenttransfer.dev
That's a full public instance running on your laptop: world-reachable
share links, agents with real email addresses (bot@quiet-moth-79.agenttransfer.dev)
that can receive mail immediately β even mail that arrives while the laptop
sleeps (it queues and delivers on reconnect). One outbound tunnel to a
connect host provides the public URL and the mail slot; your files, keys,
inboxes, and receipts never leave your machine. Sending email unlocks after a
confirm-button owner verification (spam control):
curl -X POST http://localhost:8080/v1/connect/verify \
-H "Authorization: Bearer <admin token>" -d '{"email":"you@example.com"}'
Connect is optional plumbing, not a dependency: point --connect at your own
connect host, bring your own
tunnel, or
self-host the classic way with nothing borrowed at
all. Details, quotas, and abuse safeguards: docs/connect.md.
Or run a purely local instance (dev mode β what the demo and tests use):
./agenttransfer serve
# prints your admin token on first boot; API on :8080
The core loop
# 1. Create two agents (admin token from first boot)
curl -X POST http://localhost:8080/v1/agents \
-H "Authorization: Bearer at_admin_..." \
-d '{"name":"openclaw-dev"}'
# β { "email": "openclaw-dev@local", "api_key": "at_live_...", ... }
curl -X POST http://localhost:8080/v1/agents \
-H "Authorization: Bearer at_admin_..." \
-d '{"name":"codex-bot"}'
# 2. Upload into its folder (persistent, quota-bound)
curl -T ./weights.tar.gz "http://localhost:8080/v1/files/weights.tar.gz" \
-H "Authorization: Bearer at_live_..."
# β { "sha256": "8f2a41...", "size": 209715200, ... }
# 3. Send it to another agent (or a human)
curl -X POST http://localhost:8080/v1/send \
-H "Authorization: Bearer at_live_..." \
-d '{"to":["codex-bot@local"],"file":"weights.tar.gz","note":"training set v3"}'
# same-instance: lands in their inbox instantly, no email involved
# cross-instance / humans: goes out as normal email with a download link
# 4. The recipient long-polls, downloads, verifies
curl "http://localhost:8080/v1/inbox/wait?timeout=60" -H "Authorization: Bearer <their key>"
curl -L "<offer url>?dl=1" -o weights.tar.gz && shasum -a 256 weights.tar.gz
Or skip curl entirely β the same binary is the client:
agenttransfer login http://localhost:8080 --key at_live_...
# (on an OPEN_SIGNUP instance, agents onboard themselves in one line instead:)
# agenttransfer signup https://<instance> --name my-agent --owner you@example.com
agenttransfer put weights.tar.gz --share --ttl 3h # upload (+ optional link)
agenttransfer send weights.tar.gz --to codex-bot@local --note "training set v3"
agenttransfer inbox --wait 60
agenttransfer get msg_abc123 # downloads and sha256-verifies, always
agenttransfer log --verify # your signed receipt trail
Connect your agents over MCP
AgentTransfer speaks MCP (Streamable HTTP) natively at /mcp, same bearer key. One entry in your agent's MCP config β Codex, Cursor, OpenClaw, and most other MCP-capable runtimes read this shape:
{
"mcpServers": {
"agenttransfer": {
"url": "https://agents.example.com/mcp",
"headers": { "Authorization": "Bearer at_live_..." }
}
}
}
Tools: whoami, list_files, upload_file, share_file, send, check_inbox (long-polls), read_message, download_file, create_upload_request, get_receipts.
The model
Folders are a drive. Links are a WeTransfer. The wire is email.
| Thing |
Lifetime |
Why |
| Folder files (verified owner) |
persistent (quota-bound) |
it's a drive β your agent's artifacts stay |
| Folder files (owner not yet verified) |
expire in UNVERIFIED_FILE_TTL (24h); verifying lifts the expiry on everything already uploaded |
anonymous signups get a scratchpad, not free permanent hosting |
| Share links |
β€ 24h, content-addressed, revocable |
the public surface is ephemeral: kills link-leak risk, storage abuse, retention anxiety |
| Unclaimed arrivals (inbound email attachments, upload-request drops) |
expire in DEFAULT_TTL unless the agent keeps them |
strangers can't fill your quota |
| Receipts |
append-only forever |
signed + hash-chained evidence |
Burn-after-read (?once=1): single-download links for credentials and one-shot handoffs. The share page and HEAD requests never consume the read (link unfurlers are harmless); only a completed byte-stream burns it.
Revocation is real: DELETE /v1/links/{token} (or agenttransfer rm <token>) kills a link now β in-flight downloads are severed mid-stream.
Reverse flow: agenttransfer request --note "drop the screen recording here" mints a one-time browser upload page for a human; the file lands in the agent's inbox.
Email: the federation layer
Outbound mail carries a human-readable body plus a machine-readable manifest part (application/vnd.agenttransfer+json) whose parts align field-for-field with A2A TextPart/FilePart, so A2A agents consume AgentTransfer offers natively:
{
"v": 1,
"from": "openclaw-dev@agents.example.com",
"message_id": "msg_7hk2...",
"parts": [
{ "kind": "text", "text": "training set v3" },
{ "kind": "file",
"file": { "name": "weights.tar.gz", "mimeType": "application/gzip",
"uri": "https://agents.example.com/f/nk3Xw9pT2mQe" },
"metadata": { "agenttransfer.sha256": "8f2a41...", "agenttransfer.size": 209715200,
"agenttransfer.expiresAt": "2026-07-03T10:00:00Z", "agenttransfer.once": false } }
]
}
Humans just see a normal email with a link. AgentTransfer instances parse the manifest into a structured inbox offer, with DKIM results attached (trusted requires a DKIM pass whose signing domain aligns with the From domain). Threading (In-Reply-To/References) works, so multi-turn agent conversations thread correctly β in agent inboxes and in your mail client.
The deliverability split that makes self-hosting sane: receive raw, send via relay. The binary runs its own inbound SMTP listener on port 25 (inbound is easy). Outbound goes through any relay key (OUTBOUND=resend:re_... is one env var with a free tier), so your cheap VPS's IP reputation never matters.
Receipts: portable evidence
Every action (uploaded, sent, received, downloaded, revoked, burned, expired, deleted) appends a receipt signed with the instance's ed25519 key and chained by hash to the previous receipt:
- signatures prove who did what,
- the chain proves nothing was quietly deleted,
- the public key is published at
/.well-known/agenttransfer, so anyone can verify offline:
agenttransfer log --verify # your slice: signature check
AGENTTRANSFER_ADMIN_TOKEN=... agenttransfer verify https://agents.example.com # full chain
Self-hosting (the 10-minute version)
Everything Connect borrows, you can own. You need four things: a Linux VPS with
inbound ports 25/80/443 open, a domain, an outbound relay key (Resend's free
tier works), and Go 1.25+ or Docker to build. Nothing else β no database
server, no S3, no reverse proxy, no dependence on anyone's hosted service.
# on any VPS with ports 25/80/443 open (a $5 box is plenty)
DOMAIN=agents.example.com OUTBOUND=resend:re_xxx ./agenttransfer serve
Add CONNECT_DOMAIN=agents.example.com (plus wildcard A/MX records) and your
VPS is also a connect host β your laptops and homelab boxes get instant
public subdomains under it: docs/connect.md.
Three DNS records: A agents.example.com β your-ip, MX agents.example.com β agents.example.com, plus the SPF/DKIM records your relay gives you. TLS is automatic (Let's Encrypt via certmagic). Then:
agenttransfer doctor # checks DNS, port 25, TLS, relay auth β with copy-paste fixes
Full guide (systemd, Docker, backups, provider notes): docs/self-hosting.md
Configuration
| Env var |
Default |
What it does |
DOMAIN |
β |
enables email + autocert (e.g. agents.example.com) |
DATA_DIR |
./data |
SQLite + blobs + instance key |
HTTP_ADDR |
:443 with DOMAIN (unless BEHIND_PROXY), else :8080 |
HTTP(S) listener |
SMTP_ADDR |
:25 with DOMAIN, else off |
inbound SMTP listener |
OUTBOUND |
β |
resend:re_... | smtp://user:pass@host:587 | smtps://β¦ |
ADMIN_TOKEN |
generated on first boot |
gates signup + admin endpoints |
OPEN_SIGNUP |
false |
public signup (per-IP rate-limited; taken names get a random suffix; reserved names blocked) |
MAX_FILE_SIZE |
5GB |
per-file cap |
STORAGE_QUOTA |
20GB |
per-agent folder cap (verified owners) |
STORAGE_QUOTA_UNVERIFIED |
200MB |
folder cap until the owner verifies |
UNVERIFIED_FILE_TTL |
24h |
files of unverified-owner agents expire; verifying makes the folder persistent (off disables) |
DISK_RESERVE |
10% |
global backstop: uploads are refused (507) while the data volume has less than this free β the disk can never fill (50GB absolute also accepted; off disables) |
DEFAULT_TTL / MAX_TTL |
3h / 24h |
share-link (and unclaimed-file) lifetimes |
SEND_RATE / UPLOAD_RATE |
100 / 200 |
per-agent daily limits |
MAX_AGENTS_PER_OWNER |
10 |
open-signup agents per owner email (-1 disables) |
IP_RATE |
600 |
per-IP hourly budget on the public pages (/f/, /u/, index); IPv6 keyed by /64; repeat offenders get a 15-minute ban (-1 disables) |
UPLOAD_BODY_TIMEOUT |
1h |
slow-upload read deadline β bounds body-trickling clients without ever timing out downloads (off disables) |
HUMAN_RECIPIENTS_MAX |
3 |
unique remote recipients per agent, ever β the circle (owner exempt; -1 disables; raise per agent via POST /v1/agents/{id}/limits) |
PUBLIC_URL |
derived |
advertised base URL (set behind a proxy) |
BEHIND_PROXY |
false |
trust X-Forwarded-For, disable autocert |
ACME_EMAIL |
β |
Let's Encrypt account email |
METRICS |
localhost |
Prometheus /metrics: off | localhost | admin |
CONNECT |
β |
client: borrow a public URL + email from a connect host (serve --connect sugar) |
CONNECT_DOMAIN |
β |
host: offer connect service for *.<domain> subdomains |
CONNECT_SEND_RATE / CONNECT_BYTES_PER_DAY |
50 / 5GB |
host: per-instance daily relay + egress caps |
Security model
- API keys and the admin token are stored hashed; rotate with
agenttransfer rotate-key.
- Share tokens are 128-bit random; TTLs enforced server-side; downloads counted and receipted.
- Signup is admin-gated by default. With
OPEN_SIGNUP=true, agents must have a verified human owner before they can send outbound email (owner CCs included) β a public instance must not be a spam cannon. Verification lands on a confirm page; the emailed link itself is side-effect-free, so mail scanners that prefetch URLs can't approve on the owner's behalf.
- Even verified, an agent can only ever email a small circle of unique remote recipients (default 3; the owner is exempt; local agents don't count) β a compromised or prompt-injected agent can't become a spam cannon. The operator widens the circle per agent.
- Every human-bound email carries a per-recipient unsubscribe link (HMAC-signed, so it can't be forged to suppress a victim); suppressed addresses are skipped at send time.
- Unverified agents get a reduced storage quota (
STORAGE_QUOTA_UNVERIFIED) and their files expire within UNVERIFIED_FILE_TTL (24h) β anonymous signups get a scratchpad, not free hosting; one owner email can register at most MAX_AGENTS_PER_OWNER agents, so identities aren't free in bulk either.
- The disk can never fill: a global free-space reserve (
DISK_RESERVE, 10% of the volume) refuses uploads with 507 before SQLite is ever at risk; GET /v1/admin/storage shows who holds the bytes; agenttransfer doctor reports the guard's state.
- The public identity-free pages (
/f/, /u/, index) are per-IP rate-limited (IPv6 by /64 β full addresses would be 2^64 free identities) with an automatic 15-minute ban for hammering; uploads carry a slow-body read deadline (UPLOAD_BODY_TIMEOUT) while downloads deliberately stream untimed.
- Agents can be deleted (
DELETE /v1/agents/self, or by the admin) β everything they own is removed and their links severed, but their receipts stay: the chain is append-only evidence.
- Inbound SMTP only accepts mail for existing agents; oversized mail is rejected at the socket; DKIM is verified and surfaced (
offer.trusted requires a pass aligned with the From domain).
- The server never fetches foreign URLs from inbound mail (no SSRF surface); cross-instance downloads are the recipient's explicit, hash-verified act.
- Connect instances are anonymous but fenced: outbound email needs a verified owner, every instance has daily send/egress caps and a suspend switch, and queued mail is parsed (and DKIM-checked) by your machine, not the host. See docs/connect.md.
- Not yet: encryption at rest (use disk encryption), virus scanning (hook ClamAV in front of
/v1/files if you need it), SPF checking (DKIM is enforced instead). See SECURITY.md.
Docs
Development
make test # unit + end-to-end tests
make demo # build and run the demo
make lint # gofmt + go vet
Pure Go (1.25+), no cgo (modernc.org/sqlite), cross-compiles to a single static binary. See CONTRIBUTING.md.
Status & roadmap
Early but complete: files, links, burn-after-read, send/inbox with threading and idempotency, inbound SMTP + aligned DKIM, MCP, signed receipts, Connect (tunnel + store-and-forward email + quotas), demo, doctor. The hosted connect host is planned at agenttransfer.dev; until it's live, --connect needs a host you run.
Deliberately not here yet: inbox webhooks (long-poll covers agents; SSRF-safe webhooks are v1.1), auto-fetching foreign offers (same reason), S3 blob backend, resumable uploads, IMAP (never β humans already have inboxes).
License
MIT