Building Brief-Generator: When Print Is the Product
Most web apps are judged on screen. A letter is judged after it comes out of the printer, gets folded and slides into a window envelope. If the recipient’s address doesn’t show through the window, the letter has failed, however nice the editor looked.
That’s the premise of Brief-Generator. You write a German letter in the browser, it’s laid out to DIN 5008, and it prints so the address lands in the window of a Fensterumschlag. Fold marks are on the page. Longer letters print across several pages with “Seite x von y” page numbers. You can sign with a drawn signature, one typed in a script font, or a scan. Sender profiles and a logo are there too, and everything exports to a backup file. It works offline and there’s nothing to install.
The editor (v1) and the landing page with a DIN 5008 guide (v1.1) went live on October 13. The paid Pro licence (v1.2) followed the same day.
The site is in German, because the standard and the audience are. If you’ve never had to think about where an address goes on an A4 page, the DIN 5008 guide walks through the layout rules the app follows.
Testing the printout, not the page
A typical front-end test asks whether an element exists in the DOM. That says nothing about whether the address will clear the envelope window. So the test suite prints.
Each test prints letters to PDF in real Firefox and real Chrome and then reads the PDF back. Three checks do most of the work.
The words in the address and header area are compared against a reference PDF and have to land within ±0.5 mm of their expected position. The logo box is located from rasterized pixels, so the test sees what a printer would put on paper rather than what the markup claims. And the on-screen page guides, which show in the editor where a new page will start, have to predict where the printed page breaks actually fall. A guide that’s wrong is worse than no guide, because you’d trust it.
Firefox is the reference browser. Chrome honors the CSS orphans and widows properties, so on a multi-page letter its page breaks can fall in different places than Firefox’s. If the exact break matters to you, print from Firefox.
Licences without accounts
There are no accounts. Letters, signatures and profiles stay in the browser’s IndexedDB and never reach the server. That turns the paid tier into a small puzzle: how do you sell an upgrade to someone you never identify?
Free covers up to 3 saved letters, 1 signature and 1 sender profile. Pro is a €19 one-time purchase, no subscription, and unlocks unlimited letters, signatures and profiles plus the logo.
The purchase runs through a Stripe Payment Link. Stripe fires a webhook, and a small Node server signs a licence key with ECDSA P-256 and emails it out via Brevo. The app verifies that signature offline with WebCrypto, against the public key it ships with. No login, and no licence server the app has to ask. There’s no database either. Stripe already keeps every payment, so Stripe is the record.
No framework, no build step
Brief-Generator is a static site served by nginx, running in Docker on Dokploy. The code is plain ES modules. No framework and no build step, and not a single runtime dependency. The browser loads the files as they sit in the repository.
The Content Security Policy is strict: no inline scripts and no third-party origins. For an app that stores people’s signatures, that means a script from anywhere else simply won’t run. Analytics is Plausible, self-hosted and cookie-free.
Where it falls short
Your data lives in one browser. That’s the privacy model, and it’s also the catch. There’s no sync, so moving to another device or browser goes through the backup file.
A few things a regular letter writer will miss aren’t there yet. Next on the list for v1.5: mailing notes like “Einschreiben”, search in the letter list, a reference line, a recipient address book, and a choice of fonts for the letter text.
The stack is summarized on the Brief-Generator project page. The quicker test is to write a letter at brief-generator.de, print it, and hold it up against a window envelope.