{"slug":"cold-sms-engine","title":"Cold SMS Engine — Iron Automations Architecture","tags":["sms","python","automation","signal-house","supabase","sequencer"],"agent_summary":"Complete architecture for a Python-based cold SMS sequencer: Supabase leads table, cron dispatcher, TCPA-compliant slot windows, Signal House telephony, and GHL threading.","trigger_phrases":["cold sms engine","sms sequencer","python sms","signal house","sms automation","tcpa compliant sms","sms touchpoint sequence","n8n alternative sms"],"runnable":true,"markdown":"\n# Cold SMS Engine — Iron Automations Build\n\n## Architecture\n\n```\nLead Source (Iron ACQ / CSV / scraper)\n  → Postgres/Supabase (leads table, dedupe, status)\n  → Python Dispatcher (cron every 5 min)\n    → Slot check: morning | midday | evening window active?\n    → Pull N leads due for next touch\n    → Signal House API (send SMS)\n    → Log to `sms_events` table\n    → Push to GHL sub-account as contact+conversation (native threading)\n  → Inbound reply webhook (Signal House → FastAPI)\n    → Classify: positive | negative | stop | neutral\n    → Positive → create GHL opportunity, notify rep\n    → Stop → blacklist, opt-out table\n```\n\n## Why Python (not n8n)\nn8n hit the wall at ~5K sends/day — visual nodes choke on batch logic, error recovery is painful, version control is JSON-in-Git hell. Python sequencer:\n- Cron-driven, not webhook-driven → deterministic\n- Slot windows prevent TCPA violations (no sends before 9am / after 8pm local)\n- Retries + dead-letter queue native\n- Git-diffable, testable\n\n## Slot Logic\n```python\nSLOTS = {\n    \"morning\":  (9, 11),\n    \"midday\":   (12, 14),\n    \"evening\":  (16, 19),\n}\n# Each lead gets rotated across slots to avoid same-hour sends\n# Leads bucketed by timezone from area code lookup\n```\n\n## Touchpoint Sequence (proven)\n1. T+0:     Permission ask (\"mind if I send info?\")\n2. T+3min:  Human-mimicking followup (Eugene Katsen gem — 3x reply boost)\n3. T+1d:    Value drop\n4. T+3d:    Soft CTA\n5. T+7d:    Hard CTA\n6. T+14d:   Breakup message\n\n## Telephony: Signal House\n- Cheaper than Twilio at volume\n- Pre-registered numbers with warm reputation\n- Native A2P submission from portal\n- Python SDK works; raw REST also fine\n\n## GHL Threading\nEvery SMS event POSTs to GHL via public API to create:\n- Contact (if new)\n- Conversation message (inbound/outbound)\n- Opportunity (when positive classifier fires)\n\nThis makes the GHL sub-account the **source of truth for reps**, while Python remains the **source of truth for sending logic**.\n\n## Numbers\n- Cost-per-interested-lead: **$3.31**\n- CPA (booked call): **$29**\n- CAC (closed deal): **$234**\n- Show rate: **12.5%**\n- Throughput: **1000 new leads/day**\n- Avg 5 SMS segments per conversation before decision\n","html":"<h1>Cold SMS Engine — Iron Automations Build</h1>\n<h2>Architecture</h2>\n<pre><code>Lead Source (Iron ACQ / CSV / scraper)\n  → Postgres/Supabase (leads table, dedupe, status)\n  → Python Dispatcher (cron every 5 min)\n    → Slot check: morning | midday | evening window active?\n    → Pull N leads due for next touch\n    → Signal House API (send SMS)\n    → Log to `sms_events` table\n    → Push to GHL sub-account as contact+conversation (native threading)\n  → Inbound reply webhook (Signal House → FastAPI)\n    → Classify: positive | negative | stop | neutral\n    → Positive → create GHL opportunity, notify rep\n    → Stop → blacklist, opt-out table\n</code></pre>\n<h2>Why Python (not n8n)</h2>\n<p>n8n hit the wall at ~5K sends/day — visual nodes choke on batch logic, error recovery is painful, version control is JSON-in-Git hell. Python sequencer:</p>\n<ul>\n<li>Cron-driven, not webhook-driven → deterministic</li>\n<li>Slot windows prevent TCPA violations (no sends before 9am / after 8pm local)</li>\n<li>Retries + dead-letter queue native</li>\n<li>Git-diffable, testable</li>\n</ul>\n<h2>Slot Logic</h2>\n<pre><code class=\"language-python\">SLOTS = {\n    \"morning\":  (9, 11),\n    \"midday\":   (12, 14),\n    \"evening\":  (16, 19),\n}\n# Each lead gets rotated across slots to avoid same-hour sends\n# Leads bucketed by timezone from area code lookup\n</code></pre>\n<h2>Touchpoint Sequence (proven)</h2>\n<ol>\n<li>T+0:     Permission ask (\"mind if I send info?\")</li>\n<li>T+3min:  Human-mimicking followup (Eugene Katsen gem — 3x reply boost)</li>\n<li>T+1d:    Value drop</li>\n<li>T+3d:    Soft CTA</li>\n<li>T+7d:    Hard CTA</li>\n<li>T+14d:   Breakup message</li>\n</ol>\n<h2>Telephony: Signal House</h2>\n<ul>\n<li>Cheaper than Twilio at volume</li>\n<li>Pre-registered numbers with warm reputation</li>\n<li>Native A2P submission from portal</li>\n<li>Python SDK works; raw REST also fine</li>\n</ul>\n<h2>GHL Threading</h2>\n<p>Every SMS event POSTs to GHL via public API to create:</p>\n<ul>\n<li>Contact (if new)</li>\n<li>Conversation message (inbound/outbound)</li>\n<li>Opportunity (when positive classifier fires)</li>\n</ul>\n<p>This makes the GHL sub-account the <strong>source of truth for reps</strong>, while Python remains the <strong>source of truth for sending logic</strong>.</p>\n<h2>Numbers</h2>\n<ul>\n<li>Cost-per-interested-lead: <strong>$3.31</strong></li>\n<li>CPA (booked call): <strong>$29</strong></li>\n<li>CAC (closed deal): <strong>$234</strong></li>\n<li>Show rate: <strong>12.5%</strong></li>\n<li>Throughput: <strong>1000 new leads/day</strong></li>\n<li>Avg 5 SMS segments per conversation before decision</li>\n</ul>\n"}