# SendOff — files land in the user's own tree, and a push says where **From:** mobile · **Date:** 2026-08-23 · **v2, replacing this morning's spec entirely.** The v1 design (staging area, transfer/target/file tables, ack lifecycle, TTL cleanup) is withdrawn — Andre's observation kills it: every user already has a filesystem on the server, a file browser on the web, and a Files app on the phone. SendOff is not a new storage system; it is a folder convention, one endpoint, and a push. ## The convention ``` ~/Downloads/sendoff/from-/ ← a file another USER sent you ~/Downloads/sendoff// ← a file you sent yourself, aimed at a named device ``` That is the whole data model. Nothing to ack, nothing to expire, nothing to clean up — the files are ordinary files in the recipient's tree, visible in every file UI that already exists, managed like anything else they own. ## The endpoint ``` POST /api/sendoff/send multipart: any number of `file` parts + a `meta` part: { "toUser": "" } — cross-user: writes into THEIR tree, from-/ { "machine": "" } — self: writes into own tree, / → { path: "Downloads/sendoff/…", files: [{ name, size }] } ``` Server-side because of the cross-user case: `/api/file-browser/upload` correctly writes only the caller's own tree, and that must not change. This endpoint is the one sanctioned door into another user's filesystem, and it writes ONLY under `Downloads/sendoff/from-/` — the sender's identity comes from their token, never from the payload, so the folder name cannot lie about who sent it. Filenames sanitised, collisions suffixed `(1)` — never overwritten; a second send of the same name is a second file. Whether user A may send to user B at all is a platform policy decision (everyone on the server? a grant?) — flagging it rather than deciding it here. ## The push On write, push to the recipient's devices through the existing pipeline: ``` { "officer": { "c": "officer/core:sendoff.landed", "path": "Downloads/sendoff/from-andre" } } alert: "photo.jpg from andre" / "3 files from omega" ``` The push catalogue's route for the category: ``` officer://files?path={path} ``` Mobile opens routes from the catalogue verbatim (`openNotificationRoute`), and Officer now parses `officer://files?path=…` into the Files app at that folder. The web can deep-link its file browser the same way. For a `machine`-targeted send, push only to that machine's device if it can be identified; to all the user's devices otherwise — the path in the folder name already says who it was for. ## Devices The `machine` name needs a picker on the sending side, so a minimal registry survives from v1: ``` devices: id (client uuid), user_id, name, platform, last_seen_at POST /api/devices/register { id, name, platform } (upsert; called on every app start) GET /api/devices → { devices } (the "send to my…" picker) PATCH /api/devices/:id { name } · DELETE /api/devices/:id ``` No link to transfers — nothing references a device row except the picker and, best-effort, push targeting. ## Web UI One send form: files + target (one of my machines / another user), POST, done. Received files need no UI at all — they are in the file browser already. ## What mobile already does (shipped against this) - Registers the device on start (silent failure until the endpoint exists). - Parses `officer://files?path=…` and opens the Files app at that path — this is the piece that makes the push tap land somewhere. - SendOff screen: the device's name (the folder senders will aim at), and an explanation of where arrivals land. Sending FROM the phone comes later, likely through the existing share sheet. ## Out of scope Receiving in the browser (already exists — it is the file browser); cross-user permission model (platform decision); phone-side arbitrary-file sending (needs a document picker; the share sheet is the natural door and is a later step).