new-site/scripts/workers/create_crtc_usf_campaign.py
justin 8099afc5ab CRTC USF email: note US DIDs available from Canadian carriers + point to guide
Address the obvious 'but I need US numbers' objection: several Canadian
wholesale carriers (Fibernetics, Iristel, VoIP.ms, Telnyx, Bandwidth, Twilio,
Frontier) provision US DIDs to CRTC-registered carriers, so they can keep
serving US customers from the Canadian entity. Adds a Canada-advantage bullet
and updates the guide block to call out both US + Canadian DIDs.
2026-06-17 23:53:19 -05:00

159 lines
11 KiB
Python

#!/usr/bin/env python3
"""
create_crtc_usf_campaign.py — One-off CRTC marketing email hooked on the
Q3 2026 USF contribution-factor increase.
Audience : Listmonk list 3 (FCC Carriers - Direct Contacts, ~6,046 enabled).
Hook : Q3 2026 federal USF contribution factor = 38.8% (up from 37.0% in
Q2), effective Jul 1 (FCC PN DA-26-546A1).
Offer : $200 off the CRTC carrier package service fee with code CANADA200,
valid through Fri Jun 19 2026 23:59 ET. The CRTC order page
auto-applies ?code= from the URL, so the CTA links carry it.
Lead : "Canadian Wholesale Carrier & Vendor Reference Guide" PDF, hosted at
magnet performancewest.net/guides/canada-carrier-guide.pdf (Listmonk
campaigns can't attach files, so we link a prominent download).
Creates the campaign in Listmonk as a DRAFT. Sending is a separate, manual,
STOP-and-confirm step in the Listmonk UI (or set status via API). Run:
python3 scripts/workers/create_crtc_usf_campaign.py # draft on list 3
python3 scripts/workers/create_crtc_usf_campaign.py --test # draft to a test list/email
"""
import argparse
import os
import sys
# Make `scripts` importable whether run from repo root or scripts/workers.
_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if _REPO not in sys.path:
sys.path.insert(0, _REPO)
from scripts._email_plaintext import html_to_text # noqa: E402
from scripts.workers.campaign_helpers import ( # noqa: E402
CONTACT, P, PS, H2, UL, bq, cta, hdr, flagbar, stats, assemble, ftr,
create_campaign,
)
LIST_ID = 3
CODE = "CANADA200"
GUIDE_URL = "https://performancewest.net/guides/canada-carrier-guide.pdf"
ORDER_URL = f"https://performancewest.net/order/canada-crtc?code={CODE}"
def coupon_banner():
"""Prominent $200-off banner with expiry."""
return (
'<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:8px 0 24px;"><tr>'
'<td style="background:#0f7a3d;border-radius:8px;padding:18px 24px;text-align:center;">'
'<p style="margin:0 0 4px;font-family:Arial,sans-serif;font-size:13px;color:#bdf0cf;letter-spacing:1px;text-transform:uppercase;font-weight:600;">Limited-time offer</p>'
f'<p style="margin:0 0 6px;font-family:Arial,sans-serif;font-size:22px;font-weight:800;color:#ffffff;line-height:1.2;">$200 off your Canadian carrier setup</p>'
f'<p style="margin:0;font-family:Arial,sans-serif;font-size:14px;color:#d6f5e1;">Use code <span style="font-family:\'Courier New\',monospace;font-weight:700;color:#ffffff;background:rgba(255,255,255,0.18);padding:2px 8px;border-radius:4px;">{CODE}</span> at checkout &nbsp;&middot;&nbsp; expires <strong style="color:#ffffff;">Friday at 11:59pm ET</strong></p>'
'</td></tr></table>'
)
def guide_block():
"""PDF lead-magnet download block (Listmonk can't attach files)."""
return (
'<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0;"><tr>'
'<td style="background:#f0f4ff;border:1px solid #dde5f0;border-radius:8px;padding:20px 24px;">'
'<table cellpadding="0" cellspacing="0" border="0"><tr>'
'<td style="vertical-align:middle;padding-right:16px;font-size:34px;line-height:1;">&#128196;</td>'
'<td style="vertical-align:middle;">'
'<p style="margin:0 0 4px;font-family:Arial,sans-serif;font-size:15px;font-weight:700;color:#1a2744;">Free guide: Canadian Wholesale Carrier &amp; Vendor Reference</p>'
'<p style="margin:0 0 10px;font-family:Arial,sans-serif;font-size:13px;color:#4a5568;line-height:1.6;">12 vetted Canadian wholesale partners for voice termination, SIP trunking, UCaaS, and <strong>both US and Canadian DIDs</strong> &mdash; the upstream vendors you&rsquo;ll work with once your Canadian carrier is live, and which of them can provision US numbers.</p>'
f'<a href="{GUIDE_URL}" style="display:inline-block;font-family:Arial,sans-serif;font-size:14px;font-weight:700;color:#1e40af;text-decoration:none;">Download the PDF &rarr;</a>'
'</td></tr></table>'
'</td></tr></table>'
)
def build_body():
return (
P("Hi {{ .Subscriber.FirstName }},")
+ P("If you contribute to the federal Universal Service Fund, your Q3 number just went up again.")
+ bq("The FCC has set the <strong>Q3 2026 USF contribution factor at 38.8%</strong> &mdash; up from 37.0% in Q2, and effective July 1. That is the rate you remit on the interstate and international end-user revenue you report on your 499.")
+ P("38.8% is near the highest the factor has ever been. A decade ago it sat in the mid-teens. For a small or mid-size US carrier, that is a steadily rising tax on every interstate dollar you bill &mdash; on top of everything else the FCC requires.")
+ stats(
("38.8%", "Q3 2026 USF<br>contribution factor"),
("+1.8 pts", "increase over<br>Q2 (37.0%)"),
("Jul 1", "effective date<br>(FCC DA-26-546A1)"),
)
+ H2("The US carrier burden, in one place.")
+ P("USF is just the line item that moved this quarter. The full load a registered US carrier carries:")
+ UL(
"<strong>USF contributions</strong> &mdash; now 38.8% of interstate/international end-user revenue, filed and remitted via the 499",
"<strong>Photo-ID &ldquo;Know Your Customer&rdquo; rules</strong> &mdash; under the FCC&rsquo;s 2025 Robocall Mitigation Order, you must collect and authenticate a <strong>government-issued photo ID</strong> for every new customer before you can turn up their phone service",
"<strong>FCC Form 499-A / 499-Q</strong> &mdash; annual and quarterly revenue filings, with true-ups and audit exposure",
"<strong>Robocall Mitigation Database</strong> &mdash; annual recertification; miss it and your traffic gets blocked",
"<strong>STIR/SHAKEN</strong> &mdash; call-authentication implementation and ongoing attestation",
"<strong>CALEA</strong> &mdash; lawful-intercept capability, SSI filing, and the cost of a compliant solution",
"<strong>Section 214 + Team Telecom</strong> &mdash; for international service, with national-security review that can stall financings and M&amp;A",
"<strong>State PUC registrations</strong> and <strong>FCC regulatory fees</strong> on top of the federal load",
)
+ H2("Why smaller carriers are standing up a Canadian operation.")
+ P("A CRTC-registered Canadian carrier is a separate legal entity in a separate regulatory jurisdiction. For the voice traffic you move there, the US compliance stack simply does not apply:")
+ UL(
"<strong>No USF.</strong> Canada funds its contribution program differently &mdash; there is no 38.8% factor on your Canadian carrier&rsquo;s revenue",
"<strong>No Robocall Mitigation Database recert</strong> and <strong>no FCC 499</strong> for the Canadian entity",
"<strong>No FCC photo-ID mandate.</strong> The FCC&rsquo;s government-ID &ldquo;Know Your Customer&rdquo; rule does not apply to your Canadian carrier&rsquo;s customers",
"<strong>No CALEA mandate</strong> in the US sense &mdash; lawful-intercept obligations are far lighter and cheaper",
"<strong>No Section 214 / Team Telecom</strong> &mdash; CRTC registration is a notification, not an application with a national-security review",
"<strong>Same +1 country code.</strong> Your customers dial exactly the same way &mdash; nothing changes on their end",
"<strong>US numbers still work.</strong> Several Canadian wholesale carriers provision <strong>US DIDs</strong> to CRTC-registered carriers, so you can keep serving US customers from your Canadian entity &mdash; see the free guide below for which vendors offer them",
"<strong>A clean second jurisdiction</strong> &mdash; an FCC enforcement action against your US entity does not reach a Canadian corporation",
)
+ bq("You do not give up your US business. You add a Canadian carrier alongside it &mdash; for the voice traffic that doesn&rsquo;t need to sit under the FCC, and for the Canadian market you can now sell into.")
+ H2("What we set up &mdash; turnkey, in 6&ndash;10 weeks.")
+ UL(
"Incorporation in <strong>British Columbia or Ontario</strong> &mdash; a separate legal entity from your US company",
"<strong>CRTC registration</strong> (domestic reseller + BITS international authorization)",
"Canadian DID provisioned under your new carrier identity",
"Virtual registered office, <strong>.ca domain</strong> + up to 14 email addresses",
"Full corporate binder, delivered digitally &mdash; plus a Canadian business-banking referral",
)
+ coupon_banner()
+ cta(f"Start your Canadian carrier setup &mdash; $200 off &rarr;", ORDER_URL)
+ guide_block()
+ PS(f"Questions about how the Canadian structure would work for your traffic? {CONTACT}. The {CODE} discount is good through Friday at 11:59pm ET.")
+ P("&mdash; Performance West")
)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--test", action="store_true", help="create against a test list id (env CRTC_TEST_LIST) instead of list 3")
ap.add_argument("--name", default="CRTC USF Q3 \u2014 38.8% increase + $200 off (CANADA200)")
ap.add_argument("--subject", default="USF jumps to 38.8% in Q3 \u2014 here\u2019s the Canadian alternative ($200 off)")
args = ap.parse_args()
lists = [int(os.getenv("CRTC_TEST_LIST", "0"))] if args.test else [LIST_ID]
if args.test and lists == [0]:
print("--test requires CRTC_TEST_LIST env var (a test list id)", file=sys.stderr)
return 1
body = assemble(
hdr(
"USF Increase \u2014 Q3 2026",
"USF just hit 38.8%.<br>There&rsquo;s a Canadian alternative.",
"The federal contribution factor rose again, effective July 1",
),
flagbar(
"US carrier \u2014 38.8% USF + the full FCC stack",
"Canadian CRTC carrier \u2014 no USF, separate jurisdiction",
),
build_body(),
ftr(""),
)
altbody = html_to_text(body)
print(f"=== Creating CRTC USF campaign (list {lists}) ===")
cid = create_campaign(args.name, args.subject, lists, body, altbody=altbody, status="draft")
if cid:
print(f"\nDraft created (id {cid}). Review/preview in Listmonk, then send manually.")
print(f"Body: {len(body):,} chars HTML / {len(altbody):,} chars plaintext")
return 0 if cid else 1
if __name__ == "__main__":
raise SystemExit(main())