The worst version of this doesn't announce itself. There's no error, no red banner, no failed-send log. A workflow just quietly re-enters a contact, fires the SMS step, re-enters again, and keeps going. By the time a client calls asking why one of their leads got 40 texts in an afternoon, you've already burned the segments, torched the relationship, and — if that contact happens to be a TCPA litigator — bought yourself a problem worth four figures per message.

Full disclosure: I work for Ready, an SMS platform with a native GoHighLevel integration. So I've watched this specific failure happen across a lot of sub-accounts. It's almost never a bug in GHL. It's a workflow built with a re-entry setting nobody thought about, wired to a trigger that fires on its own output.

The two patterns that cause it

Runaway sends in GHL almost always trace back to one of two structural mistakes.

1. Re-entry allowed on a trigger the workflow itself satisfies.

You build a workflow triggered by "Tag Added: hot-lead." Somewhere in that workflow, a step adds a tag, updates a field, or moves a pipeline stage. If another workflow — or the same one — watches that change and re-adds hot-lead, the contact re-enters. If re-entry is set to Allow, they run the whole thing again. And again.

The nastiest version is a single workflow that both listens for a tag and re-applies it. That's a closed loop. GHL will happily run it until you notice, because each pass looks like a legitimate, separate execution.

2. Tag-based ping-pong between two workflows.

Workflow A adds nurture-active. Workflow B triggers on nurture-active, does its thing, and removes it — then something in B (or a third automation) re-adds it. Now A fires again. Two workflows bouncing a contact back and forth, each sending an SMS on the way through. This one's harder to spot because no single workflow looks wrong in isolation.

Both patterns share a root cause: a workflow's side effects feed its own (or a partner's) entry condition. That's the thing to hunt for.

The blast radius: money first

Let's do the math, because it's more alarming than the abstract version.

Say a loop catches 50 contacts before someone kills it, and it's been running long enough to hit each of them 40 times. Your message is a 175-character promo with one emoji — unicode, so 70 chars per segment, 67 for multipart. That's 3 segments per send.

On Ready's Standard tier ($0.02/segment + $0.0045 carrier):

  • Per send: 3 × ($0.02 + $0.0045) = $0.0735
  • Per contact (40 sends): 40 × $0.0735 = $2.94
  • 50 contacts: $147

$147 in unintended sends is annoying but survivable. The problem is that "50 contacts, 40 times" is a small runaway. A tag loop that catches a 3,000-contact list before you notice — say it runs 8 times each — is 3,000 × 8 × 3 × $0.0245 = $1,764 in one incident. And that's only the number you can see on the invoice.

The blast radius you can't see: compliance and deliverability

The money is recoverable. The reputation damage isn't, at least not quickly.

Opt-outs and complaints. People who get texted 40 times don't just reply STOP — they mark the message as spam to their carrier. Carriers track complaint rate per campaign. A spike gets your 10DLC campaign throttled or flagged, which means your legitimate sends to everyone else start crawling or silently filtering. One loop can degrade delivery for the whole sub-account for weeks.

TCPA exposure. Consent to receive marketing texts is not consent to receive the same text 40 times in an afternoon. If a litigator or DNC-complainer is in that batch, each message is a potential claim in the $500–$1,500 range. Forty messages to one plaintiff is not a rounding error.

Ready's litigator and DNC scrub ($0.005/contact) suppresses known-bad numbers before a send goes out, and automatic STOP handling propagates an opt-out across every campaign so a runaway can't keep hitting someone who already bailed. But those reduce the damage — they don't stop the loop. The loop is a workflow problem, and it needs a workflow fix.

The guardrails nobody sets up

Here's the checklist I'd apply to every SMS-sending GHL workflow before it goes live. None of these take more than a few minutes.

  1. Set re-entry to "Not Allowed" by default. Only allow re-entry when you have a deliberate reason and you've confirmed the workflow's own actions can't re-satisfy its trigger. This single setting kills most closed loops outright.
  1. Add a "sent today" gate. Before any SMS step, check a custom field or date-stamp field (e.g. last_sms_sent). If it's today, skip the send. Stamp the field right after sending. Even if a loop re-enters, it can't fire the text twice in the same day.
  1. Use a wait or a frequency cap between message steps. A minimum wait between SMS steps means a loop that does spin up sends slowly enough for you to catch it before it hits 40.
  1. Never let a workflow re-add the tag that triggers it. Map your tag dependencies on paper. If Workflow A is triggered by tag-X, nothing inside A should add tag-X, and no downstream automation should either without a guard.
  1. Add a kill-switch tag. A do-not-text tag that every SMS-sending workflow checks as a filter step. When something goes wrong, one bulk tag-add on the affected contacts stops all sends instantly — faster than editing five workflows under pressure.
  1. Test with re-entry on, on one contact, watching the log. Enroll yourself. If the contact loops, you'll see it in the execution history in seconds, not after 3,000 contacts got hit.

Why the integration layer matters here

When something does slip through, the difference between a 5-minute fix and a 2-hour panic is visibility. Ready's native GHL sync means every outbound and inbound message threads back into the GHL conversation view per location. You can see, on the contact's timeline, that they got the same text six times — which tells you instantly it's a loop and not a list problem.

A webhook-based setup often loses that threading, so you find out about the runaway from the invoice or the client, not the conversation view. That's exactly when you can least afford to be blind.

This is a close cousin of a couple of failure modes I've written about before: the duplicate-send guardrails that stop the same contact getting texted six times in an hour, and the four workflow mistakes that double-text contacts. The runaway loop is the same disease in a worse stage — a duplicate that doesn't stop at two.

A 20-minute audit before you ship anything

Run this on every existing SMS workflow, not just new ones:

CheckWhat you're looking forFix if it fails
Re-entry settingSet to "Allow" without a reasonSwitch to "Not Allowed"
Trigger vs actionsWorkflow adds a tag/field that matches its own triggerRemove the self-referencing action or gate it
Cross-workflow tagsTwo workflows adding/removing each other's trigger tagsBreak the loop; add a stamp gate
SMS frequency guardNo "sent today" check before the send stepAdd a date-field gate
Kill switchNo do-not-text filter on send stepsAdd the filter tag
Timezone / quiet hoursSends going out at 6 AM localEnforce quiet hours — see the multi-timezone fix

If a workflow passes all six, a misfire can cost you a couple of duplicate texts at worst — not 40.

The practical takeaway

Runaway sends aren't a rare freak event. They're the predictable result of a re-entry setting left on default plus a workflow whose actions feed its own trigger. The fix isn't a fancier tool — it's four settings and a paper map of your tag dependencies.

Set re-entry to "Not Allowed" unless you've earned the exception. Add a "sent today" gate before every SMS step. Keep a kill-switch tag ready. And make sure your outbound messages thread back somewhere you'll actually look, so a loop shows up as a visible pattern instead of a surprise line item.

If you want the messaging layer under your GHL account to give you that visibility — with STOP propagation, quiet-hours enforcement, and litigator scrubbing already handled so a misfire does less damage — that's what Ready does. You can start with 2,500 free credits, no card required, and test a workflow safely before it ever touches a client's list.