Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
678 lines
40 KiB
Python
678 lines
40 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
create_campaigns_p2.py — Phase 2 re-engagement drip campaigns for Listmonk.
|
|
|
|
Phase 1 (7 educational emails) did not convert. Phase 2 shifts to shorter,
|
|
sharper, reply-focused emails designed to provoke conversations.
|
|
|
|
Lists:
|
|
3 = FCC Carriers - Direct Contacts (5 emails)
|
|
4 = FCC Carriers - Outside Counsel (5 emails)
|
|
5 = FCC compliance Removed - Noncompliant (2 emails: P2-1 + P2-5 only)
|
|
6 = FCC Carriers - ISP / Broadband (5 emails)
|
|
|
|
Run: python3 scripts/workers/create_campaigns_p2.py
|
|
"""
|
|
|
|
import requests, json, sys
|
|
from datetime import datetime, timezone
|
|
|
|
LISTMONK_URL = "https://lists.performancewest.net"
|
|
AUTH = ("api", "6X1rKPea61N4rZ1S65Hx5zvqzbCj30F6nvEe9oVGH_Y")
|
|
|
|
# ── Schedule: bi-weekly, alternating Monday/Wednesday, 9:00 AM CDT (UTC-5) ──
|
|
# P2-1: Mon Apr 13 P2-2: Wed Apr 29 P2-3: Mon May 11
|
|
# P2-4: Wed May 27 P2-5: Mon Jun 8
|
|
SCHEDULE = {
|
|
"p2-1": "2026-04-13T14:00:00Z",
|
|
"p2-2": "2026-04-29T14:00:00Z",
|
|
"p2-3": "2026-05-11T14:00:00Z",
|
|
"p2-4": "2026-05-27T14:00:00Z",
|
|
"p2-5": "2026-06-08T14:00:00Z",
|
|
}
|
|
# List 5 (Removed, 1 subscriber) only gets P2-1 and P2-5.
|
|
# P2-5 for removed carriers uses the P2-2 slot (Apr 29) so they don't wait 8 weeks.
|
|
SCHEDULE_REMOVED_CLOSE = "2026-04-29T14:00:00Z"
|
|
|
|
SERVICE_PAGE = "https://performancewest.net/services/telecom/canada-crtc"
|
|
ORDER_PAGE = "https://performancewest.net/order/canada-crtc"
|
|
CONTACT_PAGE = "https://performancewest.net/contact"
|
|
PHONE = "1-888-411-0383"
|
|
EMAIL = "info@performancewest.net"
|
|
CONTACT = (
|
|
f'Email <a href="mailto:{EMAIL}" style="color:#e63f2a;text-decoration:none;">{EMAIL}</a> '
|
|
f'or call <a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>'
|
|
)
|
|
|
|
# ── Footer notes ────────────────────────────────────────────────────
|
|
RMD_NOTE = ""
|
|
REM_NOTE = ""
|
|
COUN_NOTE = ""
|
|
|
|
# ── UTM helper ──────────────────────────────────────────────────────
|
|
def utm(base, campaign):
|
|
sep = "&" if "?" in base else "?"
|
|
return f"{base}{sep}utm_source=listmonk&utm_medium=email&utm_campaign={campaign}"
|
|
|
|
|
|
# ── HTML helpers (same as Phase 1) ──────────────────────────────────
|
|
|
|
def hdr(eyebrow, headline, sub=None):
|
|
s = f'<p style="margin:8px 0 0;font-family:Arial,sans-serif;font-size:13px;color:#a0b4cc;line-height:1.5;">{sub}</p>' if sub else ''
|
|
return (
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#1a2744;padding:0;">'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding:18px 40px 14px;">'
|
|
'<table cellpadding="0" cellspacing="0" border="0"><tr>'
|
|
'<td style="vertical-align:middle;padding-right:12px;"><img src="https://performancewest.net/images/logo.png" width="90" alt="Performance West" style="display:block;width:90px;height:auto;"></td>'
|
|
'<td style="vertical-align:middle;border-left:1px solid #2d4e78;padding-left:12px;"><span style="color:#8fa8d0;font-family:Arial,sans-serif;font-size:11px;letter-spacing:1.5px;text-transform:uppercase;">Telecom Services</span></td>'
|
|
'</tr></table></td></tr></table>'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e63f2a;height:3px;font-size:0;line-height:0;"> </td></tr></table>'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding:28px 40px 32px;">'
|
|
f'<p style="margin:0 0 8px;font-family:Arial,sans-serif;font-size:11px;color:#e63f2a;letter-spacing:2px;text-transform:uppercase;font-weight:600;">{eyebrow}</p>'
|
|
f'<h1 style="margin:0;font-family:Arial,sans-serif;font-size:24px;font-weight:700;color:#ffffff;line-height:1.3;">{headline}</h1>{s}'
|
|
'</td></tr></table></td></tr></table>'
|
|
)
|
|
|
|
def hdr_minimal():
|
|
"""Minimal header for plain-text style emails — logo only, no headline."""
|
|
return (
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#1a2744;padding:0;">'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding:18px 40px 14px;">'
|
|
'<table cellpadding="0" cellspacing="0" border="0"><tr>'
|
|
'<td style="vertical-align:middle;padding-right:12px;"><img src="https://performancewest.net/images/logo.png" width="90" alt="Performance West" style="display:block;width:90px;height:auto;"></td>'
|
|
'<td style="vertical-align:middle;border-left:1px solid #2d4e78;padding-left:12px;"><span style="color:#8fa8d0;font-family:Arial,sans-serif;font-size:11px;letter-spacing:1.5px;text-transform:uppercase;">Telecom Services</span></td>'
|
|
'</tr></table></td></tr></table>'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#e63f2a;height:3px;font-size:0;line-height:0;"> </td></tr></table>'
|
|
'</td></tr></table>'
|
|
)
|
|
|
|
def flagbar(left, right):
|
|
return (
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="background:#f0f4ff;padding:10px 40px;border-bottom:1px solid #dde5f0;">'
|
|
'<table cellpadding="0" cellspacing="0" border="0"><tr>'
|
|
'<td style="padding-right:16px;vertical-align:middle;"><img src="https://performancewest.net/images/flags/usa.png" width="22" height="14" alt="USA" style="display:inline;vertical-align:middle;">'
|
|
f'<span style="font-family:Arial,sans-serif;font-size:12px;color:#4a5568;margin-left:6px;vertical-align:middle;">{left}</span></td>'
|
|
'<td style="padding:0 16px;color:#cbd5e0;font-size:14px;vertical-align:middle;">→</td>'
|
|
'<td style="vertical-align:middle;"><img src="https://performancewest.net/images/flags/canada.png" width="22" height="14" alt="Canada" style="display:inline;vertical-align:middle;">'
|
|
f'<span style="font-family:Arial,sans-serif;font-size:12px;color:#4a5568;margin-left:6px;vertical-align:middle;">{right}</span></td>'
|
|
'</tr></table></td></tr></table>'
|
|
)
|
|
|
|
def stats(*items):
|
|
w = 100 // len(items)
|
|
cells = "".join(
|
|
f'<td width="{w}%" style="padding:0 6px;"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr>'
|
|
f'<td style="background:#1a2744;border-radius:6px;padding:18px 12px;text-align:center;">'
|
|
f'<div style="font-family:Arial,sans-serif;font-size:26px;font-weight:700;color:#ffffff;line-height:1;">{v}</div>'
|
|
f'<div style="font-family:Arial,sans-serif;font-size:11px;color:#8fa8d0;margin-top:6px;line-height:1.4;">{l}</div>'
|
|
'</td></tr></table></td>'
|
|
for v, l in items
|
|
)
|
|
return f'<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0;"><tr>{cells}</tr></table>'
|
|
|
|
def cta(text, url):
|
|
return (
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center">'
|
|
'<table cellpadding="0" cellspacing="0" border="0" style="margin:28px auto;"><tr>'
|
|
f'<td style="background:#e63f2a;border-radius:4px;"><a href="{url}" style="display:inline-block;padding:14px 36px;font-family:Arial,sans-serif;font-size:15px;font-weight:700;color:#ffffff;text-decoration:none;">{text}</a></td>'
|
|
'</tr></table></td></tr></table>'
|
|
)
|
|
|
|
def ftr(note=""):
|
|
note_html = f'{note}<br>' if note else ''
|
|
return (
|
|
'<!-- footer -->'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr>'
|
|
'<td style="background:#f4f5f7;padding:20px 40px;border-top:1px solid #e8ecf0;text-align:center;">'
|
|
'<img src="https://performancewest.net/images/logo.png" width="70" alt="Performance West" style="display:block;margin:0 auto 10px;width:70px;height:auto;opacity:0.5;">'
|
|
'<p style="margin:0 0 6px;font-family:Arial,sans-serif;font-size:12px;color:#9ca3af;">'
|
|
'Performance West Inc. · <a href="https://performancewest.net" style="color:#9ca3af;">performancewest.net</a></p>'
|
|
f'<p style="margin:0;font-family:Arial,sans-serif;font-size:11px;color:#b0b7c3;line-height:1.6;">{note_html}'
|
|
'<a href="{{ UnsubscribeURL }}" style="color:#b0b7c3;">Unsubscribe</a></p>'
|
|
'</td></tr></table>'
|
|
)
|
|
|
|
def bq(t):
|
|
return (
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:20px 0;"><tr>'
|
|
'<td style="border-left:4px solid #e63f2a;padding:12px 20px;background:#fdf4f3;'
|
|
f'font-family:Arial,sans-serif;font-size:15px;color:#2d3748;line-height:1.7;font-style:italic;">{t}</td></tr></table>'
|
|
)
|
|
|
|
def P(t):
|
|
return f'<p style="margin:0 0 16px;font-family:Arial,sans-serif;font-size:15px;color:#2d3748;line-height:1.7;">{t}</p>'
|
|
|
|
def PS(t):
|
|
return f'<p style="margin:0 0 16px;font-family:Arial,sans-serif;font-size:14px;color:#6b7280;line-height:1.7;">{t}</p>'
|
|
|
|
def H2(t):
|
|
return f'<h2 style="margin:24px 0 10px;font-family:Arial,sans-serif;font-size:17px;font-weight:700;color:#1a2744;">{t}</h2>'
|
|
|
|
def UL(*items):
|
|
return (
|
|
'<ul style="margin:0 0 16px;padding-left:22px;font-family:Arial,sans-serif;font-size:15px;color:#2d3748;line-height:1.7;">'
|
|
+ "".join(f'<li style="margin-bottom:8px;">{i}</li>' for i in items)
|
|
+ '</ul>'
|
|
)
|
|
|
|
def assemble(hdr_html, fb_html, body_html, ftr_html):
|
|
inner = (
|
|
hdr_html + fb_html
|
|
+ f'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding:32px 40px;background:#ffffff;" class="body-pad">{body_html}</td></tr></table>'
|
|
+ ftr_html
|
|
)
|
|
return (
|
|
'<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>'
|
|
'@media only screen and (max-width:600px){.wrap{width:100%!important;border-radius:0!important;}.body-pad{padding:24px 20px!important;}h1{font-size:20px!important;}}'
|
|
'body,table,td,p,a{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}table{border-collapse:collapse!important;}img{border:0;outline:none;text-decoration:none;}'
|
|
'</style></head><body style="margin:0;padding:0;background:#eef0f3;">'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#eef0f3;padding:20px 0;"><tr><td align="center">'
|
|
'<table width="620" cellpadding="0" cellspacing="0" border="0" class="wrap" style="width:620px;max-width:620px;background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">'
|
|
f'<tr><td>{inner}</td></tr></table></td></tr></table></body></html>'
|
|
)
|
|
|
|
def assemble_plain(body_html, ftr_html):
|
|
"""Assemble with minimal header — for personal/plain-text style emails."""
|
|
inner = (
|
|
hdr_minimal()
|
|
+ f'<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding:32px 40px;background:#ffffff;" class="body-pad">{body_html}</td></tr></table>'
|
|
+ ftr_html
|
|
)
|
|
return (
|
|
'<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>'
|
|
'@media only screen and (max-width:600px){.wrap{width:100%!important;border-radius:0!important;}.body-pad{padding:24px 20px!important;}}'
|
|
'body,table,td,p,a{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}table{border-collapse:collapse!important;}img{border:0;outline:none;text-decoration:none;}'
|
|
'</style></head><body style="margin:0;padding:0;background:#eef0f3;">'
|
|
'<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#eef0f3;padding:20px 0;"><tr><td align="center">'
|
|
'<table width="620" cellpadding="0" cellspacing="0" border="0" class="wrap" style="width:620px;max-width:620px;background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">'
|
|
f'<tr><td>{inner}</td></tr></table></td></tr></table></body></html>'
|
|
)
|
|
|
|
|
|
def create_campaign(name, subject, lists, body_html, send_at=None):
|
|
s = requests.Session()
|
|
s.auth = AUTH
|
|
payload = {
|
|
"name": name, "subject": subject, "lists": lists,
|
|
"type": "regular", "content_type": "html",
|
|
"body": body_html, "status": "draft",
|
|
}
|
|
if send_at:
|
|
payload["send_at"] = send_at
|
|
r = s.post(f"{LISTMONK_URL}/api/campaigns", json=payload, timeout=30)
|
|
if not r.ok:
|
|
print(f" ERROR {r.status_code}: {r.text[:200]}", file=sys.stderr)
|
|
return None
|
|
cid = r.json().get('data', {}).get('id', '?')
|
|
|
|
# Schedule the campaign if send_at is set
|
|
if send_at and cid != '?':
|
|
r2 = s.put(f"{LISTMONK_URL}/api/campaigns/{cid}/status",
|
|
json={"status": "scheduled"}, timeout=30)
|
|
if r2.ok:
|
|
dt = send_at[:10]
|
|
print(f" [{cid}] {name} \u2192 scheduled {dt}")
|
|
else:
|
|
print(f" [{cid}] {name} \u2192 created (schedule failed: {r2.text[:100]})")
|
|
else:
|
|
print(f" [{cid}] {name}")
|
|
return cid
|
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════
|
|
# P2-1: "THE QUICK QUESTION" (Pattern Interrupt)
|
|
# Short, personal, reply-only. No buttons, no flagbar, no stats.
|
|
# ════════════════════════════════════════════════════════════════════
|
|
|
|
print("\n=== P2-1: Quick Question ===")
|
|
|
|
# List 3 — Direct Contacts
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("I sent you a few emails over the past couple months about the Canadian carrier structure. Before I stop following up, I\u2019m curious \u2014 is this something you\u2019ve looked at and decided against, or is it more of a timing issue?")
|
|
+ P("Either way, just reply with a line and I\u2019ll know how to proceed. If it\u2019s not the right fit, no hard feelings \u2014 I\u2019ll take you off the list.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West<br>"
|
|
f'<a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>')
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-CRTC 1/5 \u2014 Quick question",
|
|
"Quick question, {{ .Subscriber.FirstName }}",
|
|
[3],
|
|
assemble_plain(b, ftr(RMD_NOTE)),
|
|
send_at=SCHEDULE["p2-1"],
|
|
)
|
|
|
|
# List 4 — Outside Counsel
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("I sent a few emails about the Canadian CRTC carrier structure as an option for your telecom clients. Before I stop following up \u2014 is this something you\u2019ve evaluated for your clients and passed on, or is it more of a timing issue?")
|
|
+ P("Reply with a line either way and I\u2019ll know how to proceed.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West<br>"
|
|
f'<a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>')
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-COUNSEL 1/5 \u2014 Quick question",
|
|
"Quick question, {{ .Subscriber.FirstName }}",
|
|
[4],
|
|
assemble_plain(b, ftr(COUN_NOTE)),
|
|
send_at=SCHEDULE["p2-1"],
|
|
)
|
|
|
|
# List 5 — Removed Carriers
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("I reached out about re-establishing your voice operation under a Canadian CRTC entity after your removal from the FCC database. Before I stop following up \u2014 is this something you\u2019ve decided against, or has the timing just not been right?")
|
|
+ P("Reply with a line and I\u2019ll know how to proceed.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West<br>"
|
|
f'<a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>')
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-REMOVED 1/2 \u2014 Quick question",
|
|
"Quick question, {{ .Subscriber.FirstName }}",
|
|
[5],
|
|
assemble_plain(b, ftr(REM_NOTE)),
|
|
send_at=SCHEDULE["p2-1"],
|
|
)
|
|
|
|
# List 6 — ISP / Broadband
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("I sent a few emails about separating your voice and data operations using a Canadian carrier entity. Before I stop following up \u2014 is this something you\u2019ve looked at and decided against, or is it more of a timing issue?")
|
|
+ P("Either way, just reply with a line and I\u2019ll know how to proceed.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West<br>"
|
|
f'<a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>')
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-ISP 1/5 \u2014 Quick question",
|
|
"Quick question, {{ .Subscriber.FirstName }}",
|
|
[6],
|
|
assemble_plain(b, ftr(RMD_NOTE)),
|
|
send_at=SCHEDULE["p2-1"],
|
|
)
|
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════
|
|
# P2-2: "THE TRIGGER EVENT" (News-Based Urgency)
|
|
# Tie to a real FCC enforcement event. Short, 2-3 paragraphs.
|
|
# Lists 3, 4, 6 only (skip List 5).
|
|
# ════════════════════════════════════════════════════════════════════
|
|
|
|
print("\n=== P2-2: Trigger Event ===")
|
|
|
|
# List 3 — Direct Contacts
|
|
# Cross-pollinate: adds the liability framing (from List 4 Phase 1)
|
|
# and the clean-separation angle (from List 6 Phase 1)
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("The FCC continues to issue enforcement orders against US carriers. The pattern is consistent: deficiency notices with shortened cure periods, followed by enforcement actions that block traffic industry-wide within 48 hours.")
|
|
+ P("Since the recent FCC orders order that removed hundreds of carriers in a single action, there have been additional rounds. Small carriers continue to be disproportionately affected \u2014 the same compliance framework, but without the legal teams that large carriers use to stay ahead of the docket.")
|
|
+ bq("The liability question is straightforward: was there a reasonable structural alternative available, and did you implement it?")
|
|
+ P("A Canadian CRTC entity separates your voice operation into a different legal entity, under a different regulator, in a different jurisdiction. The FCC can\u2019t touch it. Your US company keeps operating as before \u2014 but your voice traffic has a clean backup that no FCC order can block.")
|
|
+ cta("Talk to us about the Canadian option \u2192", utm(CONTACT_PAGE, "p2-crtc-2-trigger"))
|
|
+ PS(f"Thinking about this? {CONTACT} and I\u2019ll walk you through whether the structure fits your operation. 15 minutes, no pitch.")
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-CRTC 2/5 \u2014 FCC enforcement continues",
|
|
"FCC enforcement update \u2014 what this means for your carrier registration",
|
|
[3],
|
|
assemble(
|
|
hdr("FCC Update", "The enforcement pattern<br>hasn\u2019t changed.", "Enforcement actions continue. The Canadian structure is still available."),
|
|
flagbar("US carrier \u2014 FCC enforcement exposure", "Canadian entity \u2014 outside FCC jurisdiction"),
|
|
b, ftr(RMD_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-2"],
|
|
)
|
|
|
|
# List 4 — Outside Counsel
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("The FCC\u2019s enforcement posture against carriers has not changed. Enforcement actions continue, with shortened cure periods and immediate industry-wide traffic blocks.")
|
|
+ P("For counsel advising carrier clients: the question of whether reasonable structural protection was available and not implemented becomes more relevant with each enforcement round. A Canadian CRTC entity \u2014 a separate legal entity under a separate regulator \u2014 is the structural protection that exists today.")
|
|
+ P("If any of your clients are watching the FCC docket and considering their options, we can provide a written structure memo suitable for client review.")
|
|
+ cta("Request a structure memo \u2192", utm(CONTACT_PAGE, "p2-counsel-2-trigger"))
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-COUNSEL 2/5 \u2014 FCC enforcement continues",
|
|
"FCC enforcement update \u2014 implications for your carrier clients",
|
|
[4],
|
|
assemble(
|
|
hdr("For Telecom Counsel", "The enforcement pattern<br>hasn\u2019t changed.", "What this means for your clients\u2019 carrier registrations"),
|
|
flagbar("Client\u2019s US carrier \u2014 ongoing FCC enforcement exposure", "Canadian entity \u2014 structural protection available"),
|
|
b, ftr(COUN_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-2"],
|
|
)
|
|
|
|
# List 6 — ISP / Broadband
|
|
# Cross-pollinate: adds objection handling (from List 3 Phase 1)
|
|
# and the M&A angle (from List 3 Phase 1)
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("The FCC is still removing carriers from the FCC regulatory system in bulk. The pattern: deficiency notice, shortened cure period, enforcement action, industry-wide traffic block.")
|
|
+ P("ISPs and broadband providers that bundle voice under their main US company remain exposed. When a removal order hits, the same company name that runs your fiber plant and subscriber base is the one listed in the enforcement action.")
|
|
+ H2("The common objection we hear from ISPs.")
|
|
+ P("\u201cWe\u2019re already FCC compliant.\u201d Compliance isn\u2019t the same as protection. recent FCC orders removed hundreds of carriers \u2014 many of them compliant providers who missed a recertification detail or had a deficiency the FCC identified after the fact. Being compliant doesn\u2019t mean you\u2019re immune to a enforcement action.")
|
|
+ P("Separating voice into a Canadian CRTC entity keeps your data business completely clean. If you\u2019re ever thinking about an exit, a US broadband company with no FCC carrier obligations is a simpler acquisition than one bundled with voice compliance history.")
|
|
+ cta("Talk to us about separating voice from data \u2192", utm(CONTACT_PAGE, "p2-isp-2-trigger"))
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-ISP 2/5 \u2014 FCC enforcement continues",
|
|
"FCC enforcement update \u2014 your broadband company is still carrying voice risk",
|
|
[6],
|
|
assemble(
|
|
hdr("FCC Update", "Your data company is still<br>carrying voice risk.", "Enforcement actions continue \u2014 ISPs with bundled voice remain exposed"),
|
|
flagbar("US company \u2014 voice + data bundled, FCC risk shared", "Separate Canadian voice entity \u2014 data company stays clean"),
|
|
b, ftr(RMD_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-2"],
|
|
)
|
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════
|
|
# P2-3: "THE MICRO CASE STUDY" (Social Proof)
|
|
# Composite scenario — honest about being typical, not a named client.
|
|
# Lists 3, 4, 6 only (skip List 5).
|
|
# ════════════════════════════════════════════════════════════════════
|
|
|
|
print("\n=== P2-3: Micro Case Study ===")
|
|
|
|
# List 3 — Direct Contacts
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("Here\u2019s a typical engagement we handle.")
|
|
+ H2("The situation.")
|
|
+ P("A US-based VoIP carrier with about 15 employees was watching the FCC\u2019s enforcement action orders and realized their entire operation depended on a single regulatory status. If their FCC certification was pulled \u2014 even temporarily \u2014 all traffic stops. Their interconnect partners drop them within 48 hours.")
|
|
+ H2("The decision.")
|
|
+ P("They decided to establish a Canadian carrier entity as operational insurance. Not to replace their US operation, but to run alongside it.")
|
|
+ H2("The process \u2014 from their side.")
|
|
+ UL(
|
|
"Signed the BC incorporation articles via DocuSign (5 minutes)",
|
|
"Chose a .ca domain through our client portal (5 minutes)",
|
|
"Opened a Canadian business bank account remotely (5 minutes)",
|
|
"Reviewed the completed corporate binder when it arrived",
|
|
)
|
|
+ H2("The result.")
|
|
+ P("Six weeks later, they had a fully registered Canadian CRTC carrier entity \u2014 a separate BC corporation, CRTC BITS registration, Canadian DID, .ca domain with email, and a virtual registered office in Vancouver. Their US operation continued as before. The Canadian entity runs in parallel.")
|
|
+ P("What they didn\u2019t expect: the Canadian entity also opened a new revenue stream. Canadian DIDs, Canadian wholesale termination, and access to 38 million Canadian subscribers through CCTS membership \u2014 none of which they had before.")
|
|
+ P("<strong>Total cost: $3,899 USD. Time invested by the client: about 30 minutes.</strong>")
|
|
+ cta("See if this makes sense for your operation \u2192", utm(SERVICE_PAGE, "p2-crtc-3-casestudy"))
|
|
+ PS(f"Want to see if the structure fits your situation? {CONTACT}. 15 minutes, no obligation.")
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-CRTC 3/5 \u2014 Typical engagement walkthrough",
|
|
"How a VoIP carrier set up their Canadian entity in 6 weeks",
|
|
[3],
|
|
assemble(
|
|
hdr("A Typical Engagement", "US VoIP carrier \u2192<br>Canadian CRTC entity in 6 weeks.", "30 minutes of client time. $3,899 all-in."),
|
|
flagbar("US carrier \u2014 single point of regulatory failure", "US carrier + Canadian entity \u2014 operational redundancy"),
|
|
b, ftr(RMD_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-3"],
|
|
)
|
|
|
|
# List 4 — Outside Counsel (from counsel's perspective + referral fee)
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("Here\u2019s what a typical engagement looks like from the referring attorney\u2019s side.")
|
|
+ H2("The referral.")
|
|
+ P("A telecom attorney had a carrier client watching the FCC\u2019s enforcement orders. The client asked about structural alternatives. The attorney referred them to us for the Canadian CRTC entity setup \u2014 a compliance consulting engagement, not a legal one.")
|
|
+ H2("What the attorney did.")
|
|
+ UL(
|
|
"Made the introduction via email",
|
|
"We handled everything directly with the client from that point",
|
|
"Attorney received status updates at each milestone",
|
|
"Corporate binder delivered to attorney\u2019s file at closing",
|
|
)
|
|
+ H2("What the attorney did not need to do.")
|
|
+ UL(
|
|
"No involvement in the BC incorporation process",
|
|
"No review of CRTC filings (we\u2019re not a law firm \u2014 we handle the regulatory filings as a compliance service)",
|
|
"No billing of attorney time for the setup",
|
|
)
|
|
+ P("Six weeks later: client had a Canadian CRTC carrier entity running in parallel with their US operation. Attorney\u2019s client was protected. Attorney received a $300 referral fee, paid 14 days after the binder was delivered.")
|
|
+ P("<strong>Client cost: $3,899 USD. Attorney referral fee: $300. No ongoing obligation.</strong>")
|
|
+ cta("Discuss a referral arrangement \u2192", utm(CONTACT_PAGE, "p2-counsel-3-casestudy"))
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-COUNSEL 3/5 \u2014 Referral engagement walkthrough",
|
|
"What a carrier referral looks like from the attorney\u2019s side",
|
|
[4],
|
|
assemble(
|
|
hdr("For Telecom Counsel", "What a referral engagement<br>looks like from your side.", "$300 referral fee. No ongoing obligation."),
|
|
flagbar("Client\u2019s US carrier exposure", "Canadian entity \u2014 setup handled entirely by PW"),
|
|
b, ftr(COUN_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-3"],
|
|
)
|
|
|
|
# List 6 — ISP / Broadband (ISP-specific scenario)
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("Here\u2019s a typical engagement we handle for broadband providers.")
|
|
+ H2("The situation.")
|
|
+ P("A regional ISP offering fiber and VoIP was running both services under one US company. Their broadband operation was solid \u2014 growing subscriber base, clean regulatory history. But the FCC\u2019s enforcement action orders made them realize their data business was sharing compliance risk with their voice business.")
|
|
+ H2("The decision.")
|
|
+ P("They separated voice into a Canadian carrier entity. Their US company kept the broadband operation \u2014 no FCC carrier obligations. The Canadian company held the voice service \u2014 CRTC-registered, separate jurisdiction.")
|
|
+ H2("From the ISP\u2019s side, the process took 30 minutes.")
|
|
+ UL(
|
|
"Signed the BC articles of incorporation (DocuSign, 5 minutes)",
|
|
"Chose a .ca domain via our client portal (5 minutes)",
|
|
"Opened a Canadian business bank account remotely (5 minutes)",
|
|
"Reviewed the corporate binder at delivery",
|
|
)
|
|
+ H2("The result.")
|
|
+ P("Their US broadband company has zero FCC carrier compliance obligations. Their Canadian voice entity operates under CRTC rules \u2014 a separate regulator that has never issued a enforcement action order. Same +1 country code, no change for subscribers.")
|
|
+ P("Bonus: if they ever sell the broadband business, a clean US data company with no FCC carrier baggage is a simpler, faster acquisition. No Section 214 transfer, no Team Telecom review, no CFIUS filing on the data side.")
|
|
+ P("<strong>Total cost: $3,899 USD. Time invested by the ISP: about 30 minutes.</strong>")
|
|
+ cta("See if voice/data separation makes sense for you \u2192", utm(SERVICE_PAGE, "p2-isp-3-casestudy"))
|
|
+ PS(f"Want to talk through how the separation would work? {CONTACT}.")
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-ISP 3/5 \u2014 Typical ISP engagement walkthrough",
|
|
"How a regional ISP separated voice from data in 6 weeks",
|
|
[6],
|
|
assemble(
|
|
hdr("A Typical Engagement", "Regional ISP separates<br>voice from data in 6 weeks.", "30 minutes of client time. $3,899 all-in."),
|
|
flagbar("US company \u2014 voice + data bundled, shared risk", "US data company (clean) + Canadian voice entity"),
|
|
b, ftr(RMD_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-3"],
|
|
)
|
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════
|
|
# P2-4: "THE PRICE REFRAME" (Objection Reversal)
|
|
# Frame $3,899 against the cost of doing nothing.
|
|
# Lists 3, 4, 5 (special), 6.
|
|
# ════════════════════════════════════════════════════════════════════
|
|
|
|
print("\n=== P2-4: Price Reframe ===")
|
|
|
|
# List 3 — Direct Contacts
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ stats(
|
|
("$3,899", "Canadian carrier<br>entity, all-in"),
|
|
("$0/yr", "CRTC registration<br>fee for resellers"),
|
|
("$???", "Revenue lost per day<br>your traffic is blocked"),
|
|
)
|
|
+ P("The FCC\u2019s enforcement action orders block traffic industry-wide within 48 hours. For carriers routing even a few hundred concurrent calls, a single day of blocked traffic represents more lost revenue than the entire Canadian entity setup.")
|
|
+ P("$3,899 USD covers everything: BC corporation, CRTC BITS registration, Canadian DID, .ca domain with email, Vancouver registered office, and the complete corporate binder. No hourly billing. No surprise fees. No ongoing retainer.")
|
|
+ P("And unlike a pure insurance policy, the Canadian entity generates value: Canadian DIDs, Canadian wholesale termination, access to 38 million Canadian subscribers, CAD revenue, and Stripe Canada. It\u2019s a compliance hedge that also opens a new market.")
|
|
+ P("We also offer Klarna Pay in 4 \u2014 approximately $975/month for 4 months. Same setup, same timeline, spread across 4 payments.")
|
|
+ cta("Start your Canadian carrier setup \u2192", utm(ORDER_PAGE, "p2-crtc-4-price"))
|
|
+ PS(f"If the cost is the only thing holding you back, {CONTACT}. I\u2019d rather have a conversation than lose you to a number.")
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-CRTC 4/5 \u2014 Price reframe",
|
|
"$3,899 vs. what happens without it",
|
|
[3],
|
|
assemble(
|
|
hdr("The Math", "$3,899 all-in.<br>No hourly billing. No surprise fees.", "What one day of blocked traffic costs vs. what the setup costs"),
|
|
flagbar("US carrier \u2014 FCC exposure, no backup", "Canadian entity \u2014 $3,899 all-in, operational insurance"),
|
|
b, ftr(RMD_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-4"],
|
|
)
|
|
|
|
# List 4 — Outside Counsel
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("For context when advising carrier clients on the Canadian structure:")
|
|
+ stats(
|
|
("$3,899", "Client cost \u2014<br>all-in, fixed price"),
|
|
("$300", "Referral fee \u2014<br>paid at delivery"),
|
|
("$349 CAD/yr", "Annual<br>maintenance"),
|
|
)
|
|
+ P("The client pays $3,899 USD for the complete Canadian CRTC carrier entity setup. No hourly billing, no scope creep, no retainer. If the client wants to spread the cost, Klarna Pay in 4 is available \u2014 approximately $975/month for 4 months.")
|
|
+ P("Your referral fee is $300 USD, paid 14 days after the corporate binder is delivered. No volume requirement, no ongoing obligation. If the client doesn\u2019t complete the setup, no fee is owed.")
|
|
+ P("The relevant comparison for your client: what does one day of blocked traffic cost? For most carriers, the answer is multiples of $3,899.")
|
|
+ cta("Discuss a referral arrangement \u2192", utm(CONTACT_PAGE, "p2-counsel-4-price"))
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-COUNSEL 4/5 \u2014 Price and referral details",
|
|
"$3,899 client cost, $300 referral fee \u2014 the numbers for your carrier clients",
|
|
[4],
|
|
assemble(
|
|
hdr("For Telecom Counsel", "$3,899 client cost.<br>$300 referral fee.", "Fixed pricing, no hourly billing, no volume requirement"),
|
|
flagbar("Client\u2019s US carrier \u2014 ongoing exposure", "Canadian entity \u2014 $3,899 all-in structural protection"),
|
|
b, ftr(COUN_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-4"],
|
|
)
|
|
|
|
# List 6 — ISP / Broadband
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ stats(
|
|
("$3,899", "Voice separation<br>all-in cost"),
|
|
("$0/yr", "CRTC registration<br>fee for resellers"),
|
|
("$???", "Revenue lost when<br>your voice goes dark"),
|
|
)
|
|
+ P("If your broadband subscribers also depend on your voice service, a single FCC enforcement action event doesn\u2019t just block voice \u2014 it puts your company\u2019s name in an enforcement order that your data customers, your interconnect partners, and your lenders all see.")
|
|
+ P("\u201cBut we\u2019re already compliant.\u201d Being compliant doesn\u2019t mean being protected. The FCC removed hundreds of carriers in a single order \u2014 many were compliant. $3,899 buys you a separate legal entity that no FCC order can touch. That\u2019s not a workaround. That\u2019s structural protection.")
|
|
+ P("$3,899 USD separates voice from data. Your US broadband company stays clean. Your Canadian voice entity operates under CRTC rules. No hourly billing, no surprise fees, no ongoing retainer.")
|
|
+ P("Klarna Pay in 4 is available \u2014 approximately $975/month for 4 months.")
|
|
+ cta("Start your voice/data separation \u2192", utm(ORDER_PAGE, "p2-isp-4-price"))
|
|
+ PS(f"Want to talk through the numbers? {CONTACT}.")
|
|
+ P("\u2014 Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-ISP 4/5 \u2014 Price reframe",
|
|
"$3,899 to separate voice from data \u2014 vs. what happens without it",
|
|
[6],
|
|
assemble(
|
|
hdr("The Math", "$3,899 all-in.<br>Separate voice from data.", "What one FCC enforcement event costs vs. what the separation costs"),
|
|
flagbar("US company \u2014 voice + data bundled, shared exposure", "US data (clean) + Canadian voice entity \u2014 $3,899"),
|
|
b, ftr(RMD_NOTE)
|
|
),
|
|
send_at=SCHEDULE["p2-4"],
|
|
)
|
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════
|
|
# P2-5: "THE QUIET CLOSE" (Breakup Email)
|
|
# Last email. Personal tone. Minimal design. All lists.
|
|
# ════════════════════════════════════════════════════════════════════
|
|
|
|
print("\n=== P2-5: Quiet Close ===")
|
|
|
|
# List 3 — Direct Contacts
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("This is the last email I\u2019ll send about the Canadian carrier structure.")
|
|
+ P("If you want to set this up, $3,899 all-in \u2014 I can have the process started this week. "
|
|
f'<a href="{utm(ORDER_PAGE, "p2-crtc-5-close")}" style="color:#e63f2a;text-decoration:none;">Start here</a> or {CONTACT} and I\u2019ll handle the rest.')
|
|
+ P(f"If the timing isn\u2019t right, I get it. {CONTACT} and let me know if you\u2019d like me to check back in 3 months, 6 months, or not at all.")
|
|
+ P("Either way, I appreciate your time reading these.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West<br>"
|
|
f'<a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>')
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-CRTC 5/5 \u2014 Closing the loop",
|
|
"Closing the loop",
|
|
[3],
|
|
assemble_plain(b, ftr(RMD_NOTE)),
|
|
send_at=SCHEDULE["p2-5"],
|
|
)
|
|
|
|
# List 4 — Outside Counsel
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("This is the last email I\u2019ll send about the Canadian CRTC carrier structure.")
|
|
+ P(f"If any of your clients\u2019 FCC situations change \u2014 an enforcement action, a compliance concern, or a transaction where the carrier registration matters \u2014 {CONTACT} and I\u2019ll send the current structure memo. No commitment, no pitch.")
|
|
+ P(f"If you\u2019d like to discuss a referral arrangement: {CONTACT}. $300 per completed setup, no volume requirement.")
|
|
+ P("Either way, I appreciate your time.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-COUNSEL 5/5 \u2014 Closing the loop",
|
|
"Closing the loop",
|
|
[4],
|
|
assemble_plain(b, ftr(COUN_NOTE)),
|
|
send_at=SCHEDULE["p2-5"],
|
|
)
|
|
|
|
# List 5 — Removed Carriers (only gets P2-1 and P2-5)
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("This is the last email I\u2019ll send about the Canadian carrier structure.")
|
|
+ P("If you need to get voice traffic moving again, a Canadian CRTC entity is the fastest path \u2014 separate jurisdiction, separate legal entity, outside FCC reach. $3,899 all-in, 6\u201310 weeks, we handle everything.")
|
|
+ P(f"If you want to move forward, {CONTACT}. I can start the process this week.")
|
|
+ P(f"If this isn\u2019t the right path for your operation, I understand. {CONTACT} and let me know and I\u2019ll close your file.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West")
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-REMOVED 2/2 \u2014 Closing the loop",
|
|
"Closing the loop",
|
|
[5],
|
|
assemble_plain(b, ftr(REM_NOTE)),
|
|
send_at=SCHEDULE_REMOVED_CLOSE, # Apr 29 — don't make 1 subscriber wait 8 weeks
|
|
)
|
|
|
|
# List 6 — ISP / Broadband
|
|
b = (
|
|
P("Hi {{ .Subscriber.FirstName }},")
|
|
+ P("This is the last email I\u2019ll send about separating your voice and data operations.")
|
|
+ P("If you want to set this up, $3,899 all-in \u2014 your US broadband company stays clean, voice moves to a Canadian CRTC entity. "
|
|
f'<a href="{utm(ORDER_PAGE, "p2-isp-5-close")}" style="color:#e63f2a;text-decoration:none;">Start here</a> or {CONTACT}.')
|
|
+ P(f"If the timing isn\u2019t right, {CONTACT} and let me know if you\u2019d like me to check back in 3 months, 6 months, or not at all.")
|
|
+ P("Either way, I appreciate your time reading these.")
|
|
+ P("\u2014 Justin Hannah<br>Performance West<br>"
|
|
f'<a href="tel:+18884110383" style="color:#e63f2a;text-decoration:none;">{PHONE}</a>')
|
|
)
|
|
|
|
create_campaign(
|
|
"P2-ISP 5/5 \u2014 Closing the loop",
|
|
"Closing the loop",
|
|
[6],
|
|
assemble_plain(b, ftr(RMD_NOTE)),
|
|
send_at=SCHEDULE["p2-5"],
|
|
)
|
|
|
|
|
|
print("\nAll Phase 2 campaigns created.")
|