Fill placeholders when a template is inserted #195

Open
opened 2026-09-02 04:30:39 +00:00 by jcoffey-dev · 0 comments
Owner

Templates were a fixed subject and body, so anything that varied per message — who it is going to, today's date — had to be typed over after inserting one.

What

Eight names are recognised in both the subject and the body:

{{recipientName}} Who the message is addressed to
{{recipientFirstName}} Their first name alone
{{recipientEmail}} Their address
{{myName}} The name on the identity being sent as
{{myEmail}} That identity's address
{{subject}} The subject already on the message
{{date}} Today, in the user's date format
{{time}} Now, on the user's clock

Dates and times go through datetime.ts rather than toLocaleDateString, so a template follows the date order and clock the app was already told to use.

Decisions worth reviewing

Filled on insert, not on send. What a placeholder came to is visible in the composer and can be edited, rather than the message changing between writing it and sending it.

An unanswerable placeholder is left exactly as written. Insert a template before addressing the message and {{recipientFirstName}} stays in the body. Substituting an empty string gives "Hi ,", which is wrong rather than visibly unfinished — the name says which word is still missing and can be typed over. It is also what makes inserting a template early valid rather than a mistake to undo.

An unrecognised name is left alone too. A body that quietly ate {{nonsense}} would be worse than one that shows it.

Values are escaped into HTML. A display name comes from a contact card or a typed address; it is not trusted markup. The body is filled once as HTML and the plain-text side derived from the result, so the two cannot disagree about what a placeholder came to.

Discoverability

The names are listed under the body in the template editor, rendered from PLACEHOLDER_NAMES so one added to the lib and forgotten in the view appears without a description instead of silently not appearing.

Testing

9 new unit tests covering substitution, the unresolved and unrecognised cases, the local-part fallback, HTML escaping vs. plain subjects, and the injected clock. Full suite green: web 612, server 110, typecheck clean.

Merged 2026-09-01 as coffey-labs/ihasmail@132d0b5205

Rebuilt from: git history, session transcript.

Templates were a fixed subject and body, so anything that varied per message — who it is going to, today's date — had to be typed over after inserting one. ## What Eight names are recognised in both the subject and the body: | | | | --- | --- | | `{{recipientName}}` | Who the message is addressed to | | `{{recipientFirstName}}` | Their first name alone | | `{{recipientEmail}}` | Their address | | `{{myName}}` | The name on the identity being sent as | | `{{myEmail}}` | That identity's address | | `{{subject}}` | The subject already on the message | | `{{date}}` | Today, in the user's date format | | `{{time}}` | Now, on the user's clock | Dates and times go through `datetime.ts` rather than `toLocaleDateString`, so a template follows the date order and clock the app was already told to use. ## Decisions worth reviewing **Filled on insert, not on send.** What a placeholder came to is visible in the composer and can be edited, rather than the message changing between writing it and sending it. **An unanswerable placeholder is left exactly as written.** Insert a template before addressing the message and `{{recipientFirstName}}` stays in the body. Substituting an empty string gives "Hi ,", which is wrong rather than visibly unfinished — the name says which word is still missing and can be typed over. It is also what makes inserting a template early valid rather than a mistake to undo. **An unrecognised name is left alone too.** A body that quietly ate `{{nonsense}}` would be worse than one that shows it. **Values are escaped into HTML.** A display name comes from a contact card or a typed address; it is not trusted markup. The body is filled once as HTML and the plain-text side derived from the result, so the two cannot disagree about what a placeholder came to. ## Discoverability The names are listed under the body in the template editor, rendered from `PLACEHOLDER_NAMES` so one added to the lib and forgotten in the view appears without a description instead of silently not appearing. ## Testing 9 new unit tests covering substitution, the unresolved and unrecognised cases, the local-part fallback, HTML escaping vs. plain subjects, and the injected clock. Full suite green: web 612, server 110, typecheck clean. **Merged** 2026-09-01 as coffey-labs/ihasmail@132d0b5205af <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.