The Dead Letter Office 讓 Gemma 在瀏覽器內協助用戶為逝者寫信
The Dead Letter Office: write to someone who died, with a Gemma that never leaves your browser
The Dead Letter Office 讓 Gemma 在瀏覽器內提問,協助用戶以自己的回答寫信給逝者,並可選擇燒掉信件或儲存為裝置上的文字檔。Gemma 3 1B 首次下載約需 760 至 860 MB,Gemma 4 E2B 約需 3.1 至 3.6 GB;兩者均透過 Transformers.js 和 WebGPU 在瀏覽器運行。
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
Built with an AI coding agent. The details are under My Agent Session.
In short: a website for writing to someone who died. Gemma runs in your browser and only asks you questions; the letter is made of your own answers, and you can burn it at the end while a counter shows that nothing ever left your computer.
Dead letters! does it not sound like dead men? Conceive a man by nature and misfortune prone to a pallid hopelessness, can any business seem more fitted to heighten it than that of continually handling these dead letters, and assorting them for the flames? For by the cart-load they are annually burned.
Herman Melville, Bartleby, the Scrivener (1853)
What I Built
The Dead Letter Office is a place to write a letter to someone who died.
The real Dead Letter Office opened in 1825 to deal with mail that could not be delivered. Clerks opened it, looked for any clue to where it should go, and when there was none, the letters were destroyed. Melville gave his strangest character a past there, sorting letters for the fire.
This one works the night shift. You tell the clerk who the letter is for and one thing you remember about them. The clerk asks you three questions, one at a time, about small, concrete things: a smell, a sound, something they always said. Then your answers are set down in order as a letter. The clerk never writes a word of it. You can change everything, or ask for one more question. At the end you choose how to send it:
- Deliver by fire. The letter burns from the bottom up, the text is wiped from the page and from memory, and the office stamps a postmark.
- Return to sender. You save it as a text file on your own device.
The clerk is Gemma, Google's open-weight model, and it runs inside your browser tab. There is no server behind the page. In the top corner a counter labelled Outgoing mail shows every network request the page makes. After the model has loaded, it reads zero, and it stays at zero while you write.
For the theme, this is the ghost in the machine in the most literal sense: a mind that lives in your own computer, that you can talk to with the Wi-Fi off, and that forgets everything when you close the tab.
Demo
Live: https://nightclerk.github.io/dead-letter-office/
It works best in Chrome or Edge on a laptop or desktop with WebGPU. The first visit downloads the model once (about 760 to 860 MB for the recommended Gemma 3 1B, depending on your GPU); after that it is cached and the page works offline. If you just want to walk through the flow without downloading anything, open it with ?mock at the end of the URL: you get canned replies and a red banner saying so. In a browser without WebGPU (or with ?cpu) you get the paper clerk, explained below.
A full run with Gemma 3 1B on WebGPU, recorded in Chrome. The counter in the corner stays at 0 from the moment the clerk arrives.
Code
nightclerk
/
dead-letter-office
Write a letter to someone who died. Gemma runs entirely in your browser, so nothing you write leaves your device.
The Dead Letter Office
Dead letters! does it not sound like dead men Herman Melville, Bartleby, the Scrivener (1853)
A place to write to someone who died, with a night clerk that lives only in your browser.
Try it: https://nightclerk.github.io/dead-letter-office/ (Chrome or Edge with WebGPU; add ?mock to walk through without downloading a model, or ?cpu to see what a browser without WebGPU gets)
The Dead Letter Office helps you write a letter to someone you've lost. A small open-weight AI model, Google's Gemma, plays the night clerk: it asks a few questions to help you remember small, concrete things, then puts your words in order. When you're done you can keep the letter as a file on your device, or deliver it by fire.
The model runs entirely inside your browser tab with Transformers.js and WebGPU. There is no server, no account, no analytics, and no API key…
Plain HTML, CSS and JavaScript. No framework, no build step for the app itself, MIT licensed.
How I Built It
The model. Transformers.js v4 runs Gemma in a Web Worker on WebGPU, using the community ONNX conversions with 4-bit weights. There are three clerks to choose from, and one of them isn't a model:
| Clerk | Download | Notes |
|---|---|---|
| Gemma 3 · 1B | ≈ 760 / 860 MB | Default. Good at short, kind questions. |
| Gemma 4 · E2B | ≈ 3.1 / 3.6 GB | The newest Gemma and the best questions, for patient people with bigger GPUs. |
| The paper clerk | nothing | No model. Hand-written questions, for browsers without WebGPU and for phones. |
The first real test failed, and that's in the code now. On the first laptop it ran on, the model downloaded and then crashed while warming up: Program Sub requires f16 but the device does not support it. Not every GPU can do 16-bit float math in shaders. The app now asks the adapter for shader-f16 before choosing weights, and uses q4f16 where it is supported and q4 (32-bit math) where it isn't.
The second test taught the most important rule. We asked Gemma 3 1B to write the whole letter from a few test memories about an invented grandfather. It wrote something warm and fluent, and it gave him a favourite song that nobody had mentioned. In most apps that's a small hallucination. In a letter to someone who died, it is the one thing the tool must never do, because it puts words in a real person's mouth.
We tried a middle way next: let the model only rewrite each memory to face the person ("He peeled oranges" becoming "You peeled oranges"). It still slipped in small things, like "every evening", and it dropped others, like "every Friday". So the rule became simple: the clerk never writes a word of your letter. It only asks. The letter is made from your answers, word for word, in the order you gave them. For English, "he" and "his" become "you" and "your" through a few plain grammar rules, with no model involved. Everything stays editable.
That made the model's one job, the questions, much more important.
The questions are where the model earns its place, and they took the most work. The first prompt was a list of rules ("one question, under 20 words, don't give advice...") and the 1B model often answered with a statement instead: "The scent of rain on dry leaves always brought him joy." Two worked examples fixed that. The prompt now shows the model a memory and a good question for two other people ("He fixed bicycles in the garage on Sundays." → "What did the garage smell like when Uncle Sam was working?"), then your memory, then Question:. A small model follows an example far better than an instruction.
After the first question, the clerk looks at your latest answer, so the questions follow the conversation instead of circling back to the start. If a new question is too close to one it already asked, it tries once more, and if nothing usable comes back, it falls back to a short list of hand-written questions. The questions are not always graceful. On the test laptop, the clerk asked "Where did Grandpa Avi typically arrange his oranges to peel?", which is clumsy, and still made the right answer come out: "At the kitchen table, on top of yesterday's newspaper, next to the window."
Speed, honestly. The test laptop's GPU has no 16-bit shader support, so it runs the slower 32-bit path. Gemma 3 1B wakes from cache in about 7 seconds and asks a question in 10 to 15. Gemma 4 E2B took about 9 minutes the first time (7 to download 3.5 GB, 2 to set up) and about 27 seconds per question, and its questions were the best of any clerk: "What did the orange peel taste like?" The clerk types each question out character by character, which turns the wait into part of the ritual. Machines with shader-f16 (most recent Apple, NVIDIA and AMD GPUs) use the smaller q4f16 weights and should be faster.
Hebrew. The page is set in Frank Ruhl Libre for Hebrew and lays the letter out right to left. The questions were the problem: given a grandmother who sang while she cooked Friday chicken soup, Gemma 4 asked about "cooking the pork in the sausage." So a Hebrew letter gets five hand-written Hebrew questions instead of generated ones. It's less clever and much kinder.
A clerk that only pretends to listen is worse than none. The plan had a third Gemma: the 270M model on the CPU, for browsers without WebGPU. Its 4-bit and 8-bit builds wouldn't even load in the browser's CPU runtime (Could not find an implementation for GatherBlockQuantized). The 16-bit build loaded in 72 seconds, and when asked for a question it answered "Okay, I understand." followed by broken words, about seven seconds each time. Nobody grieving should wait a minute for that. So those browsers now get the paper clerk: no model, a short list of questions written by hand, and a note saying exactly that.
Outgoing mail. netwatch.js wraps fetch, XMLHttpRequest, sendBeacon, WebSocket and EventSource, and watches the Performance API for any other resource the page loads. The worker reports its own requests by message. It can only see this page, not your whole computer, so the page also says what the better test is: switch off your internet and keep writing.
The counter earned its place during testing. Halfway through a Hebrew letter it ticked from 0 to 1. The culprit was the page itself: the Hebrew font only loads when the first Hebrew letter appears on screen, so the browser fetched it mid-letter. Harmless, since it's the site's own file, but the promise is zero, so now every font is loaded before the clerk arrives.
Offline and self-contained. A service worker keeps the app's own files on the device. The site serves its own copies of Transformers.js, the ONNX Runtime WASM files and its fonts, so no CDN or font service sees anyone visit. Transformers.js keeps the model in the browser's Cache Storage.
Design. The goal was for it to look like something from a post office rather than an AI product. Paper and ink instead of a dark gradient, a postal red and an airmail blue, Libre Caslon for the text, Courier Prime for the clerk, who types his questions out one character at a time, and Frank Ruhl Libre so a letter can be written in Hebrew. The letter sits on airmail stationery. Burning is a CSS mask moving up the sheet with a canvas of embers and ash, and the postmark is an SVG with a rubber-stamp texture made from feTurbulence.
GitHub Actions: the clerk must not phone home. Every push runs two checks before anything is published to GitHub Pages:
- A static check fails the build if the app's own code references any outside server, apart from plain links a person can click.
- A headless Chromium walks through two letters in test mode, one in English and one in Hebrew, plus a short one with the paper clerk, pressing every button from the front desk to the postmark, and records every request. The build fails if a single request happens after the clerk arrives, if the page loads any file that isn't its own, if the office doesn't open with the network switched off, or if any screen scrolls sideways on a 360px phone.
A second workflow runs every week and checks that each Gemma model the office offers can still be downloaded from Hugging Face, and opens an issue if one has gone missing. The deploy also copies the exact library versions from npm and stamps the release on every asset URL, so a visitor never gets half old and half new code.
Why Does Open Innovation Matter?
A letter to your dead father is about the most private thing a person can write. With a closed API it would travel to someone else's server, sit in their logs, and be governed by terms nobody reads at 2 a.m. With an open-weight model running in the tab, there is nowhere for it to go. You don't have to trust a privacy policy for that. You can check it yourself: watch the counter, read the code, or unplug the router.
Open weights also make three smaller things possible:
- It costs nothing to run. There is no key, no bill and no rate limit, so the office can stay open for anyone, at any hour, for as long as GitHub Pages exists.
- It can't be switched off by someone else. The office is static files and the weights are public. If a model disappears from Hugging Face, the weekly check will notice, and anyone can point the app at another copy.
- You can shape the model's job to what it does well. When Gemma started inventing memories, the fix was to give it a smaller job it does reliably, test it locally in a loop, and pick a model size per device. Swapping between Gemma 3 1B and Gemma 4 E2B is one line, and so was trying, and dropping, the 270M.
My Agent Session
Full disclosure, and the reason the AI disclosure on this post says fully autonomous: this project was built by an AI coding agent, Claude, working on my behalf. I gave it the goal and the accounts, and then a lot of room. It chose the challenge, the idea and the name, wrote the code, did the design, tested everything in a real browser on my laptop, and wrote and published this post. I told it I trust its decisions, and I meant it.
What I find interesting is how much of the work was ordinary engineering judgment rather than generation. The agent's first instinct was to let the model write the letter. Testing it on a real machine is what changed its mind, and every important decision in this post (the f16 check, the clerk that only asks, the hand-written Hebrew questions, the paper clerk, the fonts) came from a test that failed. Some details above, like the invented song and the pork sausage, come straight from those sessions.
The Spirit of It
The moment it landed was the song. The model had written a lovely paragraph about a grandfather humming a song with a title it made up, and that paragraph showed what the tool was really for. Nobody needs a machine to write their feelings for them. What helps is someone sitting with you at night who asks you something small, like "What was the scent of his hands as he worked?" (the clerk's real question in an early test), and then hands your own words back to you, turned toward the person you lost.
This is the letter from the final test run, exactly as the office set it down:
Dear Grandpa Avi,
Every Friday you peeled oranges in one long spiral and gave me the peel to keep.
At the kitchen table, on top of yesterday's newspaper, next to the window.
A small knife with a cracked wooden handle. You never used anything else.
Your radio in the kitchen, always on the news, a little too loud.
I remember. I wanted you to know that.
Grandpa Avi is invented, for testing. Two of the three questions came from Gemma; the third was one of the hand-written fallbacks. Every sentence in the letter came from the person typing, except the last line, which the office adds and you can change. Then it burned, and Outgoing mail still said 0.
Prize Categories
- Best Use of Gemma: Gemma 3 1B runs the clerk locally in the browser on WebGPU, and Gemma 4 E2B asks the best questions on bigger GPUs. Where Gemma can't run well, the office says so and uses hand-written questions instead of a model that only pretends. Gemma's job was shaped around what it does well: it asks, and never writes the letter.
- Best Use of GitHub Copilot: GitHub Actions automates the project: a privacy gate (static check plus a headless end-to-end test that counts every request) before every deploy to GitHub Pages, and a weekly workflow that watches the models stay downloadable.
來源:DEV Community · dev.to

