Nerd stuff

ScribblePost

The privacy claim is enforced by architecture, not by policy.

ScribblePost gives children real email on the family's existing mailbox. The headline design decision: there is no ScribblePost server at all. "We don't have your mail" is not a promise — it is a property of the architecture.

Flutter / Dartenough_mail (IMAP)JMAP (RFC 8620/8621)Microsoft Graph (device-code OAuth)flutter_secure_storageproviderARB i18n (EN/NL/FR)flutter_ttsInno Setup + Azure signing

§No server, really

All traffic goes straight from the device to the mail provider. The trade-offs are written down honestly in the ADRs: web builds can only serve HTTPS-capable protocols (JMAP, Microsoft Graph) because a browser cannot open a raw IMAP socket — and the team deliberately refused to build a proxy to fake it. Even "server-side" features are emulated as ordinary mail: the parent's notifications are simply emails, sent from the same mailbox.

§One MailBackend, four worlds

A single interface (connect, getMessages, sendMessage, …) with four implementations: IMAP/SMTP via pure-Dart enough_mail, JMAP over plain HTTPS, Microsoft Graph with device-code OAuth — the parent types a short code on Microsoft's own page, so the app never sees a password and needs no redirect-URI plumbing — and an in-memory backend that doubles as the onboarding demo mailbox and the test double. Same code, both jobs.

§The PolicyEngine is a pure function

Every incoming message is classified by a stateless domain class into inbox, parent-review or blocked, based on the parent-approved contact list; unknown senders wait in review and a copy is forwarded to the parent — exactly once, tracked locally. Sending is symmetric: every recipient passes canSendTo or the compose throws. House rule: if a PolicyViolation is ever raised at runtime, that is by definition a UI bug — the action should never have been on screen. And HTML mail is never rendered: bodies are reduced to plain text below the domain boundary, so no renderer can be reintroduced by accident.

§Secrets where they belong

The account config carries no password. Credentials live in platform secure storage — libsecret on Linux, DPAPI on Windows, Keystore on Android — and the Graph refresh token rotates into the same slot on every connect. The parent PIN is stored as a salted SHA-256 hash with a single-use, 30-minute recovery code.

§Scribbles are ordinary PNGs

The drawing model is pure data (a sealed hierarchy of strokes, shapes, 18 stamp kinds and text on a fixed 400×300 canvas), rendered to PNG at 3× for crispness. The paint bucket is a real flood fill: rasterize at 2×, BFS over the RGBA buffer with a 40-per-channel tolerance to cross antialiased crayon edges, then flatten into a base layer. The result travels as a plain scribble.png attachment — grandma's mail client needs nothing special.

§Reading aloud, even on Linux

Text-to-speech uses flutter_tts everywhere it exists; on Linux, where it does not, the app shells out to speech-dispatcher (spd-say) with the reading rate slowed for young listeners. Profiles (First Steps, Beginner, Advanced) gate features by absence: what a profile does not offer is simply not in the widget tree — nothing is greyed out.

§The flow

1 interfacegeen serverFlutter-appPolicyEngine · secure storageIMAP/SMTPenough_mail · TCPJMAPRFC 8620 · HTTPSMS Graphdevice-code OAuthJouw mailboxbij je eigen provider

← Back to rm-worx.be

This website uses minimal technical storage (cookies and local storage) to ensure proper functioning and simple visit statistics. By continuing to browse, you agree to this. See our Cookie Policy and Privacy Policy.