DEV Community

Cover image for A small event tool, and the day it stopped being small
Somesh Bhardwaj
Somesh Bhardwaj

Posted on Edited on Originally published at someshbhardwaj.dev

A small event tool, and the day it stopped being small

The task was scoped as a small automation: North America only, one kind of event, some registrations and a confirmation email. The kind of thing a spreadsheet and a script would cover.

It was not that, and the tell came early. The events were multi-session, the attendees were educators across several timezones, and completion had to produce a certification that somebody would later rely on. Each of those is survivable alone. Together they mean state: who registered for which session, who actually attended, what that leaves outstanding, and what has to happen next and when. A spreadsheet holds state right up until two things change at once.

From a simple event tool to an integration-orchestrated platform

Where the complexity actually lives

Almost none of it is in the event model. Events, sessions and registrations are a straightforward schema, and if that were the whole job the original estimate would have been right.

The complexity is that nearly every meaningful fact arrives from a system you do not control. Attendance lives in the video platform. Whether a reminder was delivered lives in the email platform. Segmentation lives in the marketing tool. Whether the event is on somebody's calendar depends on an ICS file being parsed correctly by a client you have never tested.

Each of those has its own model of a person, its own idea of time, and its own failure modes. The platform's real work is translation: keeping one coherent account of what happened while four external systems each report a partial and occasionally contradictory version of it.

The production architecture, layer by layer

That is why the architecture ends up with an explicit translation boundary rather than external calls scattered through the features. When the video platform changes what it returns, the change lands in one adapter instead of in every place attendance is touched. This is the same instinct as any anti corruption layer: external models do not get to dictate internal ones, because you will be living with your internal model long after the external one has been revised.

Attendance is not a boolean

Zoom knows who joined. It does not know who they are in your system, and the join records do not reliably line up with your registrations. People join from a second device. They join late from a phone with a display name their parent chose. They attend a session they never registered for because a colleague forwarded the link.

Automatic matching handles most of it. The decision that mattered was building a manual match path for the rest, rather than either guessing or dropping them. A certification is a claim someone will make about themselves later, possibly to an employer. It should not rest on fuzzy name matching being confident enough.

Reminders as scheduled work, not delayed jobs

Reminders go out at seven days, twenty-four hours and one hour. The naive version schedules three delayed jobs per registration at signup and hopes nothing changes.

Things change. Sessions move, people cancel, someone registers ninety minutes before a session starts and needs the one-hour reminder immediately or not at all. The reliable shape is a background process that periodically evaluates current state and asks what should be sent now, rather than a queue of promises made under conditions that no longer hold. It costs more to build and it survives a rescheduled event, which the delayed-job version does not.

Background processing and the automation layer

Fifteen days, and the conversation about day sixteen

It shipped in about fifteen days as sole engineer and went into production for real educator cohort trainings, iterated under live testing first: timezone and ICS handling, mobile, and simplification of an admin UI that had grown to match the domain rather than the person using it.

The part worth writing down is what happened next. The work had clearly outgrown what was contracted, and there were two available responses: absorb it quietly, or say so. I raised it in writing and we agreed a time-boxed stabilisation window instead of an open-ended tail.

That is not a technical decision but it determined the outcome more than most of the technical ones. Scope creep on a fixed engagement is rarely a single conversation anybody refused to have. It is a series of small unremarked absorptions, each individually reasonable, and the project ends in a place neither side would have chosen deliberately. Naming it early is cheaper for the client than discovering it late, and it is the difference between a platform that gets finished and one that gets abandoned in a good-enough state.

What I would tell the version of me on day one

The estimate was not wrong about the event tool. It was wrong about how many systems had to agree for the event tool to be true. If a scope mentions attendance, certification and reminders in the same sentence, the build is an integration platform wearing a smaller job's clothes, and the honest estimate is the one that prices the translation layer rather than the schema.

Top comments (0)