← All Field Notes
· Sep 6, 2026

The Send Limit Belongs in the Code, Not in the Settings

Key takeaways

We were about to switch on an email sequence to a real list of real people. Michael's instruction was three words long and it is now a standing rule here: never oversend.

The reason it is a rule rather than a preference is that the damage is asymmetric and permanent. Ten emails a day to people who half expect them is a conversation. A hundred in an hour is spam reports, a blocked sending address, and a domain reputation that takes months to rebuild if it recovers at all. There is no undo button, no apology that fixes it, and no support ticket that reverses it.

Schedules are not safeguards

Here is the trap almost everybody falls into, including us.

You build a queue. Every item has a date. A scheduled job runs regularly, picks up whatever is due, and sends it. The pacing looks like it is handled, because the dates are spread out. Ten a day, comfortable, done.

What that design actually gives you is pacing for a queue that is behaving. It gives you nothing at all when the queue misbehaves, and there are more ways for that to happen than you would guess. The clock changes. Daylight saving shifts everything by an hour. Somebody rebuilds the queue and every item lands with today's date. An import runs twice and every record exists in duplicate. The job fails quietly for three days and then succeeds, and suddenly four days of work is due simultaneously.

In every one of those cases, the sender wakes up and finds a pile of items that are all legitimately due right now. It will send every single one of them, because nothing in the code says otherwise. The dates were never the safeguard. They were only ever a plan.

Five ordinary events, from a clock change to a backlog that finally clears, turn a paced queue into one pile of simultaneously due items

Put the ceiling where every path has to go through

The fix is not clever. Put hard limits inside the script that does the sending, as constants, above every other line.

Not in a scheduled task's arguments, because arguments get changed and tasks get re registered. Not in a configuration file, because the thing that oversends is usually a loop that never read the configuration. Not in the interface, because the interface is not what runs at two in the morning. In the code, in the one function that every send has to pass through.

Our reference sender has four guards, and each one covers a hole the others do not.

The first is a per run ceiling that clamps whatever it is asked for. A caller requesting nine hundred and ninety nine still gets three. This matters because the caller is often a person testing something, or another script, or an argument nobody has looked at in a month.

The second is a per day ceiling. Before anything goes out, the script counts what has already gone out today by reading the queue file itself, rather than trusting a separate counter that can drift away from reality. If today's count is at the ceiling, the run stops cold rather than shipping one more.

The third is that every send is written to disk the moment it happens. A crash halfway through cannot lose the tally and let the next run start counting from zero.

The fourth is deduplication across the whole campaign. Someone who appears on two lists gets contacted once, ever.

Four guards on a sender: a per run ceiling, a per day ceiling read from the queue, an immediate write to disk, and a campaign wide duplicate check

The bug that made the rule specific

The first version of that sender had a counter, and the counter looked correct. It incremented after each send.

Then a permissions problem meant the sending service refused every single call. The counter never moved, because nothing was ever succeeding. So the loop kept going, and it walked all five hundred and sixteen records in the campaign in one run, attempting each one in turn.

Nothing went out that time, and it would be easy to file that under no harm done. It is the opposite. Consider the same run where the failures stop halfway through, because a rate limit lifts or a token quietly refreshes. Everything after that point sends, in one burst, in a couple of minutes, to hundreds of people.

The lesson is one sentence. Count attempts, not successes, and spend the budget before the network call rather than after it. A cap that only counts what worked is not a cap at all.

And test it the way it was caught, which is not the happy path. Force every item due, ask for a preposterous number, and confirm the run stops at the ceiling. If you have only ever tested a well behaved queue, you have tested nothing.

What this means for your business

Somebody could point out that a limit written into code can be raised by editing one line, so it is not really a hard limit. That is true and it misses the point. Nobody is defending against a person who wants to send more. They can, and sometimes they should. The thing you are defending against is a loop, and a loop does not edit source files. It just keeps going.

So the question worth asking about any tool that sends on your behalf, whether you built it or bought it, is a simple one. What stops this if the queue is wrong. If the answer is the schedule, you do not have an answer. If it is a setting somebody can override, you have half of one.

This is the unglamorous part of what a GHL consultant Bay Area business should be doing for you. Not just wiring the sequence up, but deciding in advance what it is never allowed to do.

Want this built for you

We build outreach and follow up systems for small businesses, with the ceilings written in before the first message goes out. Start at optechsol.llc.

Want this working in your business?
Get my plan ← Back to all Field Notes