Help

Attachments, and what they would actually cost

3 September 2026. Written in response to: "consider whether we can/should support the camera and photo sending, videos? … what about other attachments of reasonable size … do the math just to have an idea for the future."

The headline, before the numbers

The intuition that we cannot afford attachments is correct on Netlify and wrong in general. Netlify meters bandwidth; every byte a phone collects is billed. Object stores built for this job meter operations and give egress away. On Cloudflare R2 the bytes are close to free and the cost is the number of requests.

That inverts the conclusion. On R2, video is cheaper than photos — not because video is small, but because people send one video where they send three photos, and requests are what we pay for.

And it exposes the real cost driver, which is neither:

> Polling costs more than every attachment combined. At 100,000 users, payload runs about > $80/month and polling about $1,300/month. Push notifications are not a nicety; they are the > difference between those two numbers.

The numbers

Verified 3 Sep 2026 from the vendors' own pricing pages.

Netlify (what we run today): bandwidth 20 credits/GB; web requests 2 credits per 10,000; functions compute 10 credits per GB-hour. A Pro credit is about $0.0067, so bandwidth is roughly $0.13/GB. Blobs storage is not separately published — it draws on the same credit pool.

Cloudflare R2: storage $0.015/GB-month, Class A (writes, deletes, lists) $4.50/million, Class B (reads) $0.36/million, egress free. Free monthly: 10 GB, 1M Class A, 10M Class B.

### What a message costs to carry

The server is a relay, not an archive: a message is deleted the moment it is confirmed, so steady-state storage is only what is in flight, not everything ever sent. Bandwidth, by contrast, is paid twice on every delivered byte — once in, once out.

Sizes used below: voice note (Opus 24 kbps, 30 s) 90 KB; photo at WhatsApp-grade compression 150 KB; photo at good quality 500 KB; 30-second video at WhatsApp-grade 3 MB.

Monthly cost of payload, by scale — Netlify bandwidth vs R2 operations:

Traffic1k users10k users100k users100k on R2
Text only (50 msg/day)$0.40$4$40~$8
Voice notes (2/day, 90 KB)$1.44$14$144~$50
Photos (3/day, 150 KB)$3.60$36$360~$77
Photos (3/day, 500 KB)$12$120$1,200~$77
Video (1/day, 3 MB)$24$240$2,400~$24

The R2 column barely moves with size and moves a lot with frequency. That is the whole point: three photos a day costs three times one video a day, even though the video is twenty times bigger. We are not paying for storage. We are paying for requests.

### And then there is polling

Every background check is a request whether or not a message is waiting. At 100,000 users checking every 15 minutes, that is 96 checks a day, about 288 million requests a month:

Against $77/month for the photos those checks are looking for. Polling is 5–17× the cost of the payload, and it scales with users, not with usage — a user who sends nothing at all still costs us every fifteen minutes, forever.

This is the strongest argument yet for finishing push. A wake-up push costs nothing on either platform (APNs and FCM are free), and it replaces the poll that costs everything. It also happens to be the better product: messages arrive when they arrive rather than up to fifteen minutes later. The privacy story does not change — the push carries no content, only "wake up," which is what DECISIONS.md already says it is.

So: should we ship attachments?

Photos and voice notes: yes, and the cost is not the reason to hesitate. At any scale we can plausibly reach in the next two years, compressed photos on R2 cost less per month than a single hour of contract work. The reasons to hesitate are engineering, not money.

Video: technically affordable, but it is the biggest job on the list. It needs transcoding, codec choices, thumbnails, playback, and scrubbing — and every one of those is a place to leak metadata or ship a crash. The cost math says yes; the calendar says much later.

Other attachments (PDFs, documents): nearly free to carry and cheap to build, because there is no rendering problem — hand the file to the OS and let it open. If anything ships after photos, this is the one with the best ratio of usefulness to work.

### What the engineering actually requires

Not in the protocol — the sealed envelope does not care what is inside it. The work is at the edges:

  1. Raise MAX_ENVELOPE_BYTES. It is 32,768 today (server/netlify/functions/_shared.mts:20), which is a text-message limit. Attachments need chunking above some threshold rather than one enormous envelope, so that a failed transfer resumes instead of restarting.
  2. Strip EXIF before encrypting. A photo carries GPS coordinates, the camera's serial number, and a timestamp. Shipping photo support without stripping these would undo, in one feature, every location claim we make. This is not optional and it is not a later refinement.
  3. Thumbnails, so a conversation does not download megabytes to draw a list.
  4. Progress and resume. A 3 MB transfer over a bad connection fails often enough that "try again from the start" is not acceptable.
  5. *The direct path gets better here, not worse.* A photo over Wi-Fi Direct is free, fast, and never touches a server — the pitch is stronger for photos than it is for text, because the saving is visible. Sending a holiday album across a table with no internet at all is a demo nobody else can give.

### The migration this implies

If attachments ship, the mailbox should move to R2 and Netlify should keep the API. It is not a hard migration — deposit/collect/confirm are the only functions that touch storage — and it is worth roughly a factor of ten immediately and much more later. Doing it before attachments is easier than doing it after, because there is less in flight to move.

One caveat worth stating rather than discovering: Cloudflare would then see our traffic patterns. It never sees plaintext, and it never learns identities, because the tokens are already rotating and the envelopes are already sealed. But it is a second party in the path, and the privacy policy would have to say so plainly. That is a real cost, not a bookkeeping one.

What this does not change

Nothing here argues for storing more, keeping it longer, or knowing more about who sent it. The 30-day ceiling, the rotating tokens, the delete-on-confirm, and the absence of logs are all unaffected by how big the payload is — which is exactly why attachments are a cost question rather than a privacy question. The privacy question is EXIF, and the answer is: strip it.

English is the reference text; any translation is a convenience. Help is stored on this phone and never contacts anyone.