How photos and ETAs get attached
An update that just says “in progress” is barely better than silence. What turns it into something a customer trusts is a photo of their actual job and a straight answer to “when?” This post is about those two attachments: how a snap taken at the bench reaches the right job, and how an honest ETA for the next stage is worked out rather than guessed.
Key takeaways
- A photo taken at the bench lands in S3 under a key built from the job id and the stage, so it attaches itself to the right update.
- A small handler resizes the photo and links it to the job; an oversized or unreadable file is dropped, never sent broken.
- Photos are optional. A move with no photo still sends a perfectly good update — the picture is a bonus, not a blocker.
- The ETA is computed, not guessed: the next stage’s typical duration from settings, plus an honest buffer, rounded to a friendly time.
- If the shop hasn’t set a duration for the next stage, no ETA is shown — better silent than wrong on a promise.
Why a photo and a time matter so much
An update that only says “your job is in progress” is honest but thin. Two things turn it into something a customer actually trusts. The first is a photo: a picture of their own phone opened on the bench, their bike on the stand, their suit pinned for alteration. It’s proof that real work is happening to their real thing, and it does more for confidence than any wording. The second is a straight answer to the only question the customer really has: when? “In progress” without a when still leaves them guessing. This post is about those two attachments — where the photo comes from, and how the ETA is worked out so it’s honest rather than hopeful.
How a photo reaches the right job
The shop already takes photos — most benches have a phone within reach. The trick is getting a snap to the right job without anyone doing fiddly admin. The photo is dropped against the job: uploaded from the board row, or sent to a shared folder named for the job. However it arrives, it lands in the jsnr-photos S3 bucket under a key built from the job id and the current stage — something like J-2042/in-progress.jpg. That key is the whole linking mechanism. When the composer gathers facts for a move to in progress on J-2042, it looks for exactly that key, and if a photo is there, it’s the one attached to this update.
A small handler, jsnr-photo, tidies each upload as it lands: it confirms the file really is an image, resizes it down to something sensible for a text or email (a phone photo can be several megabytes; nobody wants that arriving as an MMS), strips location data, and records the link on the job. A file that isn’t a valid image, or is absurdly large, is dropped with a log line rather than passed on — a broken attachment is worse than none. The bucket keeps versioning on, so a replaced photo doesn’t silently lose the old one, and a lifecycle rule clears photos a while after the job is collected, since there’s no reason to store a stranger’s repair photos forever.
Crucially, photos are optional. If a move happens and no photo was dropped, the update sends anyway with no gap and no apology — the composer simply doesn’t mention a picture. The photo enriches the message when it’s there; it never holds one up when it isn’t.
How an honest ETA is built
The ETA is the part most worth getting right, because a missed promise does more damage than no promise at all. So it’s computed from facts, never guessed by the model. The shop settings carry a typical duration for each stage — diagnosing takes about half a day, in progress about two days, awaiting parts depends on the supplier. When a job moves into a stage, the composer looks up how long the next step typically takes, adds that to now, applies an honest buffer so the shop is more likely to beat the estimate than miss it, and rounds the result to a friendly point — “by Thursday afternoon”, not “at 15:42 on Thursday”.
A few rules keep it honest. The buffer always pushes the estimate later, never earlier — it’s better to say Thursday and be ready Wednesday than the reverse. The estimate respects opening hours, so a job that finishes Friday evening reads “ready Saturday morning” rather than implying a midnight collection. And the load-bearing rule: if the settings have no duration for the next stage — or there is no next stage, because the job just reached ready — then no ETA is produced, and the composer is told there isn’t one. As Part 3 covered, the model is forbidden from inventing a time the facts didn’t carry. A missing ETA means a quieter message, not a made-up one.
The payoff, and the restraint
Put together, these two attachments are what make a one-line text feel like a shop that’s on top of things. The bike-repair customer gets a photo of their gears stripped down and “ready Saturday morning”; the tailoring-studio customer gets a snap of the pinned hem and “ready for a fitting Wednesday afternoon.” The photo proves it’s real; the ETA lets them plan. Neither needed anyone to write anything.
The restraint is just as important as the richness. A photo is dropped rather than sent broken; an ETA is omitted rather than guessed. A customer would forgive a plain “in progress” far sooner than they’d forgive a confident “ready Thursday” that turns out to be Monday. By computing the time from real durations, always buffering later, and falling silent when there’s nothing solid to say, the system keeps the one currency that matters here: a customer who believes what your messages tell them.
Design rules that shaped photos and ETAs
- The key does the linking. A photo stored under job-id and stage attaches itself to the right update.
- Tidy or drop. Validate and resize every photo; a broken attachment never goes out.
- Photos are optional. No picture is a quieter message, never a blocked one.
- Compute the ETA, don’t guess it. Next-stage duration plus an honest buffer, rounded and within opening hours.
- Buffer late, or stay silent. The estimate only ever moves later; no duration set means no ETA at all.