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 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.
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:
| Traffic | 1k users | 10k users | 100k users | 100k 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.
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:
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.### 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.
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.