Writing
Building the same thing four times
August 28, 2026
Over the years at my day job I’ve built “the Slack integration,” then “the Gmail add-on,” then “the Zapier app,” then “the Teams integration.” On a resume that reads like four projects. In reality it’s one feature built four times — and somehow each rebuild taught me something the previous three hadn’t.
Slack humbled me first. Its Events API wants a 200 back within three
seconds, and if you don’t make it, it assumes you’re dead and retries. My
first handler did the actual work inline — fine on a quiet Tuesday, but the
moment anything got slow, every event started arriving three or four times and
users got the same message in triplicate.
The fix generalizes to every webhook you’ll ever receive: acknowledge immediately, throw the real work on a queue, and make the handler safe to run twice — dedupe on the event ID, or design the operation so a replay is a no-op. Platforms promise at-least-once delivery on a good day. “Exactly once” is your job, not theirs. I learned this in production, which is apparently the only place I learn anything.
Then there’s OAuth, which is technically one standard and practically four. Slack handed me a token that just… never expired. Google’s refresh tokens die quietly for half a dozen reasons — the user changed their password, revoked access, or your app sat in testing status too long. Microsoft adds a tenant admin who has to bless the whole thing before your users can even consent.
The real lesson took me longer: token death is a product problem, not plumbing. Somebody’s connection will break through no fault of yours or theirs, and the “this stopped working, reconnect here” flow is a screen actual humans will see. Build it on purpose, with words in it, instead of letting a background job fail silently for three weeks (ask me how I know).
I’ve also learned to treat these APIs like weather, not infrastructure. Rate
limits, random 500s, the occasional multi-hour outage — that’s not an
emergency, that’s Tuesday. So nothing calls a platform API inline from a user
request anymore. It goes on a queue, backs off exponentially, honors
Retry-After, and logs everything — because when someone says “the Slack
thing didn’t work,” I want to answer with evidence, not a shrug.
The mistake I was most confident about: sharing the UI layer. “Show a card with two buttons” is Block Kit JSON on Slack, an Adaptive Card on Teams, and CardService calls in the Gmail add-on — three incompatible dialects for the same idea. I tried to build one renderer to rule them all. It did not rule them all. What actually shares is the layer underneath — what happened, and which actions are legal — with a thin, deliberately boring adapter per platform on top.
Zapier turned out to be different in kind: no UI at all, just triggers and
actions for an integrator you will never meet. That forces a discipline the
others don’t — stable IDs (Zapier dedupes on id, so regenerate them and
everyone’s Zaps re-fire), honest sample payloads, pagination that actually
works. Designing for a faceless robot customer quietly made our core API
better for the humans too.
I’d love to tell you the fifth integration will go smoothly, now that I’ve collected all this wisdom. It won’t. Some platform will find a brand-new way to surprise me, and that’s fine — that’s the job. If there’s one thing to take from all this: integration work isn’t glue code. It’s a discipline of its own, mostly about assuming the other side will let you down politely.
— Adi