How It Started
The platform already had a read API. Customers could query their own records whenever they wanted, and for a long time that was enough. Then the integration requests started arriving from the other direction: rather than polling us, larger customers wanted us to reach out to them, on a cadence they chose, in a format their existing tooling already understood. Some wanted an HTTP callback. Some wanted a file. Some wanted the file to land on a server they operated.
That is a different product from an API. An API is a request that either succeeds or fails while somebody is watching. Scheduled delivery runs unattended at 06:00 on a Tuesday, and the first person to notice a problem is a customer whose overnight import produced nothing. Everything about the design followed from that: the schedule needed to be unsurprising, the outcome needed to be recorded, and the whole thing needed to survive running on more than one machine at once.
One Profile, Four Questions
The unit of configuration is a delivery profile, and it answers exactly four questions: what data, filtered how, on what schedule, sent where. Everything else on the row is derived state rather than user intent, which turned out to be the important distinction. Its enabled flag, its health, the timestamp of the last successful run, and a status reason explaining why it is in the state it is in are all things the server owns. The user owns the intent; the server owns the consequences.
Destinations were modelled as a discriminated type from the first commit rather than bolted on later. There was exactly one destination when the feature shipped, and the abstraction looked like over-engineering for about six weeks. It stopped looking like over-engineering the moment a second destination arrived with completely different failure modes, and it is the reason part three of this series is a feature and not a rewrite.
Why the Schedule Is Not a UTC Timestamp
A customer who asks for a delivery at 06:00 means 06:00 where they are, permanently. They do not mean the UTC instant that happened to correspond to 06:00 on the day they filled in the form. If you store the instant, the delivery quietly moves by an hour twice a year, and it moves in the direction that puts it before the previous day's close of business exactly when that matters most.
So the profile stores local wall-clock time plus an IANA time zone, and the next fire time is computed from the zone's rules every time the scheduler evaluates it, rather than precomputed and cached. That makes daylight-saving transitions a property of the calculation instead of a migration you have to remember to run twice a year. It also means the transition edge cases, the hour that does not exist in spring and the hour that happens twice in autumn, are handled by a well-tested library rather than by us.