Kamali · AI · Email
Thirty copies, each written for its reader
Mail merge puts a name at the top. This marks what should differ for each funder, translates once, writes those parts per reader, and never sends anything twice.
The same project update often goes out to dozens of funders. Mail merge would swap the name and the greeting. That isn't personalisation, so I removed the token substitution from Kamali weeks ago. What a funder notices is whether the document talks about the part they paid for.
Mark what should differ
The flow has four steps, and the first is a scan. One model call reads the document and marks two kinds of block: what must differ between recipients (the amount, the project they fund, last year's milestone) and what could be made personal. Anyone can click a block to add or remove it. The scan is cached, so opening the document again costs nothing.
Translate once
Then the document is translated once per language, not once per funder. If the translation states a figure the original doesn't, it's reported. If the translated text runs longer and no longer fits its box, it's shortened once and reported too. Long documents are translated section by section with a shared list of terms, because a fixed page count was leaving the last pages untranslated.
Personalise only the marked blocks
Each funder's copy rewrites only the marked blocks, from that funder's own record. The register (du or Sie, vous or tu) belongs to the covering email, not the document. A document that's marked final is never changed.
Send once, ever
The step I'm most careful with is the last one. A document goes to an address once, ever. That's enforced on the server: sending claims a row that's unique per document and address, so a double click, a retry or two colleagues on the same screen can't send it twice. A failed send gives the row back.
Emails leave from the sender's own address and every one is logged, with a status per recipient from the mail provider: delivered, bounced, opened. Today the first real batch went out. I'll admit I hesitated before pressing "send all". Watching the statuses turn green one by one is the best test this feature has had.
Any list, any format
People don't have tidy contact lists. So you can paste a list or drop a spreadsheet as it is. A guest list with no column hints goes to the model as text and comes back as contacts, none missed, for about 4,000 tokens. The language of each contact is guessed by a small library, not by a model.