← The Iris Lab
Real NumbersJuly 6, 2026·7 min read

WhatsApp-grade images on a chat widget, with zero image servers.

A 12-megapixel phone photo is 4–12 MB. It has no business crossing the wire at that size, and it never does — a few lines of canvas code re-encode it to 250–400 KB before it leaves the browser, strip the GPS metadata as a privacy bonus, and hand a tiny WebP to an object store that caches it forever. The whole image stack: no resize server, no thumbnail worker, no transform CDN. Here are the numbers.


When a customer pastes a screenshot into a support chat, or an agent drags a photo of a damaged parcel into a reply, the raw file is enormous — a modern phone photo runs 4 to 12 MB. That file has no business travelling across the network or sitting in storage at full size, and in our chat it never does. Before a single byte is uploaded, a few lines of canvas code shrink it to a 250–400 KB WebP, strip the camera's hidden metadata, and hand the result to an object store that will cache it, unchanged, forever. The entire image infrastructure — resize, thumbnail, format conversion, delivery — costs us zero dedicated servers, because the expensive work happens on a device we don't own.

This is a Real Numbers post, so here are the numbers first: a 10–30× size reduction, done in the browser in a fraction of a second, for the price of no infrastructure at all. The rest is how, and why the boring version of this problem quietly costs other teams a whole subsystem.

The naive version, and what it costs

The obvious way to handle uploaded images is the server way, and it's the way most tools grew up doing. The client uploads the full-size original; a server (or a queue of workers) receives it, decodes it, resizes it to a few standard widths, re-encodes each to a web-friendly format, writes them all to storage, and hands back URLs. Every one of those steps is real compute, real memory, and real operational surface — an image can be a decompression bomb, a malformed file can wedge a worker, and the whole thing has to autoscale with your busiest hour.

That subsystem is the classic server-side thumbnail pipeline, and it's exactly what the big chat and helpdesk tools run under the hood. It works. It also means that the moment your product accepts images, you've signed up to operate an image-processing service — with all the capacity planning and 3am pages that implies. For a small team, the cheapest image server is the one you never deploy.

Move the work to the client

The insight is that a browser already contains a complete, hardware-accelerated image pipeline — it's called the canvas. The user's device just decoded that 12-megapixel photo to display it in the file picker; asking it to also resize and re-encode the thing is nearly free, and it happens on a processor you aren't paying for.

So the flow inverts. Instead of uploading the original and processing it server-side, we process it first, in the browser, and upload only the small result:

The whole image pipeline runs in the browser, before a byte is uploaded
1
Decode
Draw the chosen photo onto an off-screen canvas
2
Cap
Scale the longest edge down to 2048px — plenty for any screen
3
Re-encode
Export as WebP near quality 82; the camera’s EXIF is dropped on the way out
4
Upload
Send the small file under a unique key to an object store behind a CDN
No resize server, no thumbnail worker, no image-transform CDN. The most expensive part of image handling happens on a device you don’t pay for. Stripping EXIF is a privacy bonus — the customer’s GPS coordinates never leave their phone.

Four steps, all client-side. Draw the chosen photo onto an off-screen canvas. Scale the longest edge down to a 2048px cap — more than enough resolution for any screen a support conversation is read on. Export the canvas as WebP at roughly quality 82, the sweet spot where the format's compression is invisible to the eye but brutal on the byte count. Then upload that — a small, clean file under a unique key — to an object store sitting behind a CDN. No server ever touches the original. It never leaves the device.

The most expensive part of image handling is the part you can make someone else's computer do.

What the giants actually do

It's worth checking this against the tools that have solved image handling at planetary scale, because the numbers line up. WhatsApp, famously, compresses aggressively: as of mid-2026 it resizes shared photos so the longest dimension sits around 1600 pixels and re-encodes them (JPEG on the default photo path), typically landing a 200–400 KB file that looks identical to the source on a phone screen (WhatsApp image size guides, 2026). That is the entire trick: nobody sends full-resolution photos through a chat, because nobody needs to. A support thread is read on a screen, not printed on a billboard.

Our numbers are deliberately a notch more generous than WhatsApp's — a 2048px cap instead of ~1600, and WebP instead of JPEG, which buys sharper text in screenshots at a smaller size. Slack and Intercom and the rest reach a similar end state from the other direction, generating their web-sized renditions on the server after you upload the original. Same destination, different bill: they pay a server to shrink the image; we pay the browser that was going to decode it anyway.

The numbers, on one real photo

Here's a representative photo walking through the pipeline — the same file, measured at each stage:

One real phone photo through the pipeline — where the bytes go
Original phone photoa 12-megapixel JPEG/HEIC straight off the camera
4–12 MB
After the 2048px capfewer pixels left to encode
~1–2 MB
After WebP near q82the modern format does the heavy lifting
250–400 KB
A 10–30× reduction, done client-side in a fraction of a second. The upload is faster, the chat feels instant, and the bandwidth bill is a rounding error — because the file that crosses the wire is already small.

A 4–12 MB original becomes a 250–400 KB WebP. Call it a 10–30× reduction depending on the source photo, achieved before the upload even starts. The knock-on effects are the whole point: the upload finishes in a blink instead of a stall, the chat feels instant because the payload is tiny, and the bytes you store and serve for the lifetime of that conversation are a small fraction of what the naive path would have kept. The fastest upload is the one that's already small when it starts.

The privacy bonus nobody asked for

Re-encoding through a canvas has a side effect that turns out to matter more than the bytes: it strips the EXIF metadata, and EXIF is where the secrets hide. A photo straight off a phone can carry the exact GPS coordinates where it was taken, the device model, the timestamp, sometimes more. When a customer sends a picture of a defective product photographed in their living room, the original file may quietly encode their home address in latitude and longitude.

The server-side pipeline has to remember to scrub that. Our client-side re-encode drops it for free — the canvas export produces a clean image with none of the original's metadata, because a canvas only knows pixels. The customer's location never leaves their device, because the file that leaves their device was born on the canvas, coordinates already gone. The most private data is the data that was never transmitted.

Cache it once, serve it forever

The last piece is delivery, and it's where "zero servers" becomes literally true. Every uploaded image gets a unique, content-addressed key — two different uploads never collide on the same URL. That single property unlocks the most aggressive caching posture there is: because a given URL's bytes can never change, the object can be marked immutable and cached forever, at the edge, everywhere.

The first person to view an image pulls it from the origin store. Every viewer after that — the agent, the customer, the teammate who gets looped in tomorrow — is served from a CDN edge a few milliseconds away, and the origin is never troubled again. No cache-invalidation logic, no versioning headaches, no thundering herd, because immutability makes all of those problems undefined. A URL that can never change is a URL you can cache until the heat death of the universe.

Fail soft, always

One honest caveat, because it's the difference between a demo and a product: the client-side path has to fail soft. An ancient browser, an exotic image format the canvas can't decode, a device low on memory — any of these can make the re-encode step throw. When it does, the code doesn't block the send; it falls back to uploading the original, size caps and server-side guards still in place. A photo that arrives large is a minor cost; a photo that fails to send is a broken product. The optimization is the common case, not a hostage the feature depends on.

Add it up and the whole image stack — resize, format conversion, metadata scrubbing, thumbnailing, delivery — is a canvas, an object store, and a CDN with a long cache header. No resize service to operate, no worker fleet to autoscale, no transform CDN bill that grows with your traffic. Attachments in the inbox and the email channel just work, arrive small, and cost, in dedicated infrastructure, exactly nothing. That's not a shortcut we took; it's the design. The best server is the one that isn't in the diagram.

Put this playbook to work.

Create a workspace, paste one snippet, publish a few articles. Free to start — live before your coffee cools.

Keep reading

Real NumbersJuly 6, 2026

Self-hosting the observability stack: the real math.

ResearchJuly 6, 2026

We tested two frontier models on a real customer-facing support agent. Here’s who lied.