GTM Engineering
The GTM Engineer's Definition of Done
It fires in production is not done. Idempotent, observable, reversible, documented, handed off. Here is the checklist that separates a demo from a system reps can trust.
· 13 min read
The flow worked in the demo. It fired on the test record, the field updated, everyone nodded, and it shipped. Three weeks later it ran twice on the same record because an integration retried, double-counted a deal, and blew a rep’s number. Nobody could tell when it had last run, nobody had captured what the field held before, and the person who built it had moved on with no note left behind. It “worked.” It was never done. The gap between those two words is where most GTM automation lives, and it is where the 3am pages come from.
Software engineering settled this argument decades ago with a definition of done: code compiling is table stakes, and the finish line is tested, observable, reversible, and documented. GTM engineering skipped that lesson, because it grew out of ops, where “the flow fired” felt like the finish line. The flow firing is where the real work starts. A GTM system touches money, and money makes reversibility and observability non-negotiable. Here is the definition of done I hold every build to, as an actual checklist you run before you call anything shipped.
Done is not one thing you check at the end. It is five gates that compose, each one leaning on the one below it, and a build climbs them bottom to top. An automation that is not safe to run twice cannot be trusted enough to observe, and a system you cannot observe cannot be reversed with any confidence, and so on up. Scroll the ladder and watch the gates light in order: that ascent is the whole framework, and the rest of this piece walks each rung.
- G5Handed off, someone else can own it3am test
A short doc: what it does, what it touches, how to turn it off, how to roll it back, where the log lives. If only the builder can fix it, the build is a single point of failure with a pulse. Done is defined by the person who did not build it.
- G4Tested on the edges, not the demo5 cases
The demo tests the happy path. Done tests the null field, the record with no owner, the batch of 10,000, the renamed picklist value. Production is all edges, so the edges are the test. A clean-record test proves nothing about production.
- G3Reversible, you can put it back0 undo
The gate money makes non-negotiable. The API has no undo button; the only undo is the prior values you captured before the write. A bad run on 500 records is a replay from the rollback table, or it is days of manual cleanup.
- G2Observable, you can see it ranquery it
A run log answers "did it run today" and "what did it change" with a query, not a guess. Without it, the first you hear of a failure is a rep saying a number is wrong. A last-run timestamp, a records-touched count, and a change log.
- G1Idempotent, safe to run twiceno-op
Running the same record twice is a no-op. The double-run is the single most common GTM production failure: an integration retries, an event fires twice, a batch reprocesses. Stamp the record and short-circuit on a repeat. Nothing above this rung matters if the build double-fires.
”It fired” is the start of the work, not the end
The seductive thing about GTM automation is that the happy path is easy and looks like the whole job. A flow that updates a field on the record it was built to test will fire, and firing feels like done. But a system does not live on the happy path. It lives in production, where records arrive malformed, integrations retry, two events race, and the person debugging it at 3am is not you and has never seen the code. Done means the system survives all of that, not that it survived the demo.
So the definition of done is a set of questions the happy path never asks. What happens when this runs twice? How would I know it ran at all? If it did the wrong thing to 500 records, how do I put them back? When it breaks in a quarter, can someone who is not me fix it? If I answer any of those with a shrug, it is not done, it is deployed, and those are different words.
The five gates
The ladder above is the order, and it is not arbitrary. A build clears done only when it clears all five, bottom to top, because each gate depends on the ones beneath it. Here is what each rung asks of the code.
Gate 1: idempotent, safe to run twice
The single most common production failure in GTM automation is the double-run: an integration retries, an event fires twice, a batch reprocesses, and the automation does its thing again on a record it already touched. The demo never shows this because the demo runs once. Done means running twice is a no-op. Stamp the record with what the automation did and short-circuit on a repeat.
// Not done: this increments every time it runs, so a retry double-counts.
opp.Touch_Count__c = opp.Touch_Count__c + 1;
// Done: guarded by a stamp, so a re-run is a no-op.
if (opp.Last_Processed_Key__c != runKey) {
opp.Touch_Count__c = opp.Touch_Count__c + 1;
opp.Last_Processed_Key__c = runKey; // replaying this run changes nothing
}
This is the idempotent automations discipline, and it is gate one because nothing downstream matters if the thing double-fires. An automation that is not safe to run twice is not safe to run. The payments world settled this a decade ago: Stripe requires an idempotency key on every write request precisely because networks retry and clients time out, and a retry without a key creates a duplicate charge. AWS makes the same point about the retry itself, recommending exponential backoff with jitter because fixed-interval retries stampede and, on a non-idempotent endpoint, multiply the damage. GTM automation touches the same failure surface with none of the same discipline, which is why the double-run tops every incident list I have seen.
Gate 2: observable, you can see it ran
If you cannot tell whether an automation ran, when it last ran, and what it did, you are flying blind, and the first you will hear of a failure is a rep complaining a number is wrong. Done means the automation writes a trail: a last-run timestamp, a count of records touched, and a log of what changed. When someone asks “did the scoring job run today,” the answer is a query, not a guess.
-- Every automation writes to a run log. "Did it run?" is a query, not a guess.
SELECT job_name, run_at, records_processed, records_changed, errors
FROM automation_run_log
WHERE job_name = 'opportunity_stage_scoring'
ORDER BY run_at DESC
LIMIT 5;
Gate 3: reversible, you can put it back
This is the gate money makes non-negotiable. An automation that writes to records must capture what those records held before it wrote, so a bad run is reversible. There is no undo button on the API; the only undo is the prior values you saved. Done means you captured them before the write, not that you hoped you would not need them.
-- Before any bulk write, snapshot current values keyed to the run.
INSERT INTO change_rollback (run_key, record_id, field, old_value, new_value, changed_at)
SELECT :run_key, id, 'stage_name', stage_name, :new_stage, now()
FROM opportunity WHERE id IN (:target_ids);
-- Rollback is now a replay of old_value where run_key = the bad run.
The same control-layer thinking from before you give an agent write access applies to your own automations. You are one bad WHERE clause away from needing this table, and it only exists if you built it before the run.
Gate 4: tested on the edges, not the happy path
The demo tests the happy path. Done tests the cases the happy path hides: the null field, the record that arrives with no owner, the batch of 10,000, the picklist value that got renamed. A test that only proves the automation works on a clean record proves nothing about production, where records are not clean.
| Edge case | What breaks without the test | The assertion done requires |
|---|---|---|
| Null on a field you read | NullPointerException, silent skip | Runs clean, handles null explicitly |
| Duplicate event | Double-count, double-assign | Second run is a no-op (gate 1) |
| Bulk of 10,000 | Governor limit, timeout | Batches within limits |
| Renamed picklist value | Rule silently stops firing | Fails loudly or maps the new value |
| Record with no owner | Assignment to null, orphaned record | Routes to a fallback queue |
Gate 5: handed off, someone else can own it
The last gate is the one everyone skips because it is not code. If the only person who can fix this build is the person who built it, that build is a single point of failure with a pulse, no matter how well it runs. Done means a short doc: what it does, what it touches, how to turn it off, how to roll it back, and where the run log lives. The test is simple: could a teammate disable this safely at 3am without calling you? If not, hand it off before you call it done.
Each gate on the ladder resolves to one concrete artifact you can point at. If the artifact does not exist, the gate is not cleared, no matter how confident the demo felt. This is the checklist in table form: five gates, five things that must physically exist before the word “done” applies.
| Gate | The question it answers | The artifact that proves it | Absent this, the failure is |
|---|---|---|---|
| G1 Idempotent | What happens on a re-run? | A stamp or key that makes the second run a no-op | Double-count, double-assign |
| G2 Observable | Did it run, and what changed? | A run log row per execution | Silent failure, found by a rep |
| G3 Reversible | How do I put 500 records back? | A rollback table written before the run | Days of manual cleanup |
| G4 Tested | Does it survive a bad payload? | Tests for null, dupe, bulk, renamed, ownerless | Exception or silent skip in prod |
| G5 Handed off | Can on-call own this without me? | A one-page runbook | A build only its author can fix |
Where builds actually fail in production
The reason to hold every gate is that production failures cluster exactly where the happy path did not look. This is roughly the distribution of GTM automation incidents I have seen, and every slice maps to a gate that was skipped.
View as table
| Item | Value |
|---|---|
| Not idempotent | 34% |
| No rollback | 26% |
| Untested edge case | 22% |
| Not observable | 12% |
| No handoff | 6% |
”It fired” done vs engineering done
Two builds, same feature. One is a demo that shipped. One is a system.
| "It fired" done | Engineering done | |
|---|---|---|
| Runs twice | Double-counts, blows a number | No-op, stamped and guarded |
| Did it run today? | Nobody can say | One query against the run log |
| Bad run on 500 records | No undo, manual cleanup for days | Replay prior values from the rollback table |
| Null or renamed value | Silent skip or exception | Handled and tested explicitly |
| Breaks while you are out | Waits for you, or someone guesses | On-call disables and reverses from the doc |
| What it is | A demo that reached production | A system reps can trust |
Here’s how I’d build it: the definition-of-done checklist
Run this before you call any build shipped. It is five checks, and it is the difference between a feature and a liability.
- 1
Prove it is idempotent
Run it twice on the same record and confirm the second run changes nothing. Guard every write with a stamp or a conditional. If running twice is not a no-op, the automation is unsafe to run even once in production.
- 2
Make it observable
Write a run log: timestamp, records processed, records changed, errors. "Did it run" and "what did it do" must be a query, not a guess. You cannot trust what you cannot see.
- 3
Make it reversible
Capture prior values before any write, keyed to the run. The API has no undo; the rollback table is the only one you get, and it only exists if you built it before the run, not after the incident.
- 4
Test the edges, not the demo
Write tests for null fields, duplicate events, bulk volume, renamed values, and missing owners. The happy path is not a test. Production is all edges, so the edges are the test.
- 5
Hand it off
Write the short doc: what it does, what it touches, how to disable it, how to roll it back, where the log lives. If only you can fix it, it is a single point of failure, not a finished build.
Every gate on this list is an afternoon of work the demo told you to skip. The demo only ran the happy path, and production never does. Try this today: take your last “done” build and run it twice against the same record. If the second run is not a no-op, you just proved it was deployed, not done. That one build is your first candidate for the checklist, and the checklist is the only thing standing between a flow that fired and a system reps can trust with their numbers.
Keep reading
One email. Every week.
One email a week: a system I built or broke, with the config, the numbers, and what I would change. No roundups, no theory, unsubscribe whenever it stops being useful.
The newsletter opens soon.
Connect a provider in src/config.ts