Generating letters from code
Not every letter is typed into the app. An importer turning an archive into
articles, a billing system issuing invoices, a bot posting minutes — each needs to
produce signed .thing files directly.
Everything required is in src/format/ (the future @souspli/format package): pure
TypeScript, no Electron, no network, runs in Node and in browsers, and never holds a
key — it signs through a Signer interface you supply.
import { buildBundle, admitBundle } from './src/format/index.js'
const bundle = await buildBundle(signer, { // signer: your Signer (eth-eip191)
program, // Uint8Array — e.g. samples/memo.html
type: 'memo',
args: { to: 'All staff', from: 'Ops', subject: 'Friday', message: '…' },
attachments: new Map([ // name -> { bytes, mime? }
['plan.png', { bytes: png, mime: 'image/png' }],
]),
})
// `bundle` is the bytes of a .thing file.
Use the same program bytes as the built-in type (samples/<type>.html) if you
want readers' apps to recognise the type: a type is identified by the hash of its
program, so a reformatted copy is a different type.
Per-type guides
The repository carries step-by-step guides for the types most worth generating —
the exact args schema, attachment rules and limits for each. They are written as
Claude Code skills, so an AI
coding agent working in this repository picks them up automatically, and they read
fine as plain documentation:
| Guide | Covers |
|---|---|
create-article |
Articles from a scraper or importer: blocks, media placement, provenance. Read this one first; the others build on it. |
create-memo |
Memos and correspondence with enclosures; which attachment types render. |
create-poster |
Photo sets: arrangements and attachment naming. |
create-invoice |
Invoices: the integer-only money model (minor units, thousandths, basis points) that a float will break; totals derived, not supplied. |
Things that bite
- No floats, anywhere. Canonical encoding rejects them.
19.99must be1999. - Text is NFC-normalised or the manifest is rejected.
createdis a claim. Readers order by when they received a letter.- Verify what you made: run it through
admitBundlebefore shipping it, then open one in the app. A program ignores fields it does not read, so a manifest that contains your data proves nothing about what a reader will see. - Volume is not reach. Generated letters still have to get to people — as files, via a magnet, or posted to a relay they subscribe to.