Toss files between devices from the browser, and keep an always-on inbox on your computer that your phone (or any browser) can send to. Runs entirely on Cloudflare Workers. Nothing is stored: files go device to device over WebRTC when possible, otherwise through a relay that forwards bytes without keeping them.
Status: M0 prototype. Running at https://toss2.me, which is also the CLI default server.
- Nearby: open the page on two devices on the same network and they see each other (grouped by public IP, like Snapdrop/PairDrop). Tap a device, pick files, the other side accepts.
- Codes: on different networks, one device creates a 6-digit code and the other enters it.
- Text messages: tap a device and type or paste instead of picking files; the inbox page has a message box too. A message is a UTF-8
.txtentry flaggedtext: truein the transfer protocol (up to 1 MB), so it takes every path a file does: the receiving page shows it with a Copy button and clickable links,tosstome listenprints it and saves the.txt, andtosstome send --textsends one. - Direct first, relay second: transfers try a WebRTC data channel (STUN via
stun.cloudflare.com) and fall back to a Durable Object relay after 8 seconds. On the same network a direct transfer never leaves it. This holds for the CLI too: browser totosstome listen(inbox link and--nearby), andtosstometo a browser on a code, all go direct when they can. The CLI gets WebRTC fromnode-datachannel, an optional dependency; without it, or withTOSSTOME_DIRECT=0, the CLI uses only the relay. - Inbox:
tosstome listenkeeps an always-on inbox on a computer and prints a link and QR code. Opening the link on a phone sends files straight to that computer's folder. - MCP (
tosstome mcp): a stdio MCP server so coding agents can receive files from a phone (get_inbox_link,wait_for_files,list_received_files) and send files or text back (send_fileswithpathsand/ortext, to a 6-digit code or an inbox link). Answers both the 2026-07-28 per-request_metarevision and the legacyinitializehandshake. The inbox starts on first use, so only run one inbox holder at a time. - Nearby inbox (
--nearby): the listening computer also appears in the Nearby list of browsers on the same network. Those files go to a separatenearby/folder, because anyone on that network can send them.
server/: the Cloudflare Worker and its Durable Objects. AGPL-3.0.cli/: thetosstomecommand line tool and MCP server. MIT.npm installincli/copiesshared/intocli/shared/so the published package is self-contained; runnpm run prepare --prefix cli(ornpm test, which does it first) after editingshared/.shared/: the transfer protocol, WebRTC and device-name code used by the CLI and by browser clients. MIT.web/: a plain browser client (static HTML, CSS and ES modules, no build tool) with every feature of the site: Nearby, codes, files and text messages both ways, and the inbox send page at/i.web/build.mjscopiesshared/intoweb/shared/; wrangler runs it beforedevanddeploy, and the Dockerfile runs it too. Includes qrcode-generator (MIT) inweb/vendor/.tests/: end-to-end tests against a running server.
The toss2.me website (its own design, guides and translations) is not part of this repository, but web/ does everything the site's app does, so a self-hosted server is usable from a browser out of the box. Any client that uses shared/ can talk to the server the same way.
npm install # test dependencies
cd server && npm install && npx wrangler dev --port 8787 # API and browser client at http://127.0.0.1:8787
cd cli && npm install
node cli/bin/tosstome.js listen --server http://127.0.0.1:8787cd server && npx wrangler deploy # uses wrangler's own loginThis deploys the API and the browser client in web/: every path outside /api/ is served from it. To serve a different client, point the assets block in server/wrangler.jsonc at its directory.
Uses only Workers and SQLite-backed Durable Objects, so the Workers Free plan is enough to try it. Workers do not charge for egress; relayed transfers are billed as Durable Object duration while the relay is active.
docker run -d -p 8080:8080 -v tosstome-data:/data tosstome/tosstomeThe image runs the same Worker code in workerd, Cloudflare's open source Workers runtime, with Durable Objects kept as SQLite files in /data (only inbox key hashes are stored there; files never are). It serves the browser client from web/ at /, and inbox links open its /i page. It listens on plain HTTP port 8080, so put a reverse proxy with TLS in front of it for use over the internet. See docker-compose.yml.
Nearby grouping needs the client address, which workerd does not see. Set CLIENT_IP_HEADER to the header your reverse proxy overwrites with it (for example X-Real-IP, or X-Forwarded-For, of which the last entry is used). Never set it to a header the proxy passes through from clients, or anyone could join another network's Nearby list. Without it, every device lands in one group, which is fine for a server reachable from only one network.
Build it yourself with docker build -t tosstome .. The docker GitHub Actions workflow builds the image, runs the tests below against it, and on pushes to main and v* tags publishes linux/amd64 and linux/arm64 images to Docker Hub; pushes to main also copy this README to the Docker Hub page. It needs the DOCKERHUB_USERNAME and DOCKERHUB_TOKEN (Read, Write, Delete) secrets and the DOCKERHUB_IMAGE variable (such as yourname/tosstome).
browser / CLI
| WebSocket
Worker on Cloudflare or workerd in Docker (/api routing, web/ as static assets)
|-- Room DO presence + WebRTC signaling (one per network or code)
|-- Relay DO blind byte relay, a fixed pool of 8 shared objects so concurrent transfers share duration billing
`-- Inbox DO one per inbox: key check, hands sender and agent a relay session
The transfer protocol (shared/transfer.js) is shared by browser clients and the CLI: JSON control frames plus binary chunks, with end-to-end acks so flow control behaves the same over WebRTC and the relay. The sender picks the path; the receiver waits on both and takes whichever connects. WebRTC signals travel through the Room (nearby and codes) or the Inbox (inbox links). A CLI agent says it takes direct connections (rtc: true in its nearby accept and inbox auth), so clients never wait on WebRTC with an agent that cannot answer.
- An inbox has two secrets created by the CLI: an agent key (never leaves
~/.config/tosstome/inbox.json) and a send key carried in the inbox link's#fragment. The server stores only their SHA-256 hashes. Keys are sent in the first WebSocket message, not in URLs. - Wrong keys are counted per role: senders guessing keys lock out senders, never the owner's agent.
- File names from senders are reduced to a plain name inside the target folder; files are written as
.partand renamed when complete. - Not yet: end-to-end encryption on the relay (traffic is TLS to Cloudflare only), signed device pairing, TOTP/CPace guest sessions, rate limits. These are M1.
node tests/integration.mjs http://127.0.0.1:8787 # relay, inbox, auth, nearby, rooms
node tests/rtc_node.mjs http://127.0.0.1:8787 # WebRTC direct path via node-datachannel
node tests/mcp.mjs http://127.0.0.1:8787 # tosstome mcp over stdio: versions, inbox, code room, cancelserver/ and everything not marked otherwise: AGPL-3.0 (LICENSE). cli/ and shared/: MIT (their own LICENSE files).