Shipmind Labs

The worst notification bug we've shipped wasn't a delivery failure. It was a message that arrived perfectly — reading "Your order {order_id} has shipped."

Nothing errored. The queue was green. The provider returned 200. The customer got a literal placeholder.

This is what happens when a template is treated as a string instead of a contract. Most notification code renders whatever it's handed, and a missing key becomes an empty string or a leftover brace. The pipeline has no opinion about it, because the pipeline never knew what the template needed in the first place.

So in our open-source notify-dispatch we made the template declare its own variables and their types up front. Rendering isn't a string operation anymore — it's a call against a signature. Values get validated at dispatch time, before any provider sees them. A forgotten order_id raises where it belongs: in the test that builds the payload, not in someone's inbox.

The expert take: in multi-channel systems this matters more than it looks. The same event fans out to email, push and messenger templates, each with a slightly different variable set, each maintained by a different person on a different day. Without declared variables you have four silent contracts and no way to test them together. With them, adding a channel is a schema change you can review.

A notification is the one part of your system that a customer reads word for word. It deserves the same type discipline as an API boundary.

How do you catch template drift today — schema validation, snapshot tests on rendered output, or does it only surface when support forwards the screenshot?

Was this useful?

Building something similar?

or email hello@shipmindlabs.com