new-site/scripts/workers/create_crtc_usf_campaign.py
justin 720197095c CRTC USF email: defensible framing + conversational-voice caveat
Reframe away from 'escape the FCC' optics that would draw enforcement attention:
- Header/flagbar: 'Move your VoIP home to Canada' / 'US obligations ride on your
  upstream' (was 'no FCC reporting, no USAC, no S/S to run')
- Recast claims to 'CRTC regulatory home, not FCC' and scope the no-USF/no-499/
  no-RMD claims to the Canadian-jurisdiction traffic (accurate for US-number
  traffic, which rides on the compliant US upstream)
- STIR/SHAKEN bullet now explicitly pro-compliance: 'we don't help anyone dodge
  call-authentication; upstream partners are fully S/S compliant'
- Drop 'outside the FCC's reach'
- Add honest caveat: Canada is not for short-duration/dialer traffic; Canadian
  carriers are more stringent on ACD/ASR than anywhere; this is for real
  conversational voice (UCaaS/PBX/business/residential/live-agent)
2026-06-18 00:20:44 -05:00

162 lines
13 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 US carriers remit on the interstate and international end-user revenue they report on the 499.")
+ 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)"),
)
+ P("38.8% is near the highest the factor has ever been &mdash; a decade ago it sat in the mid-teens. And USF is just one line on a long list. Here is the full load a US-registered voice provider carries today:")
+ UL(
"<strong>USF contributions</strong> &mdash; 38.8% of interstate/international end-user revenue, filed and remitted to USAC via the 499",
"<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; standing up and running your own call-authentication / signing posture",
"<strong>Photo-ID &ldquo;Know Your Customer&rdquo; rules</strong> &mdash; under the FCC&rsquo;s 2025 Robocall Mitigation Order, collecting and authenticating a government-issued photo ID for every new customer before you turn up service",
"<strong>CALEA</strong> &mdash; lawful-intercept capability and SSI filing",
"<strong>Section 214 + Team Telecom, state PUC registrations, FCC regulatory fees</strong> &mdash; on top of all of the above",
)
+ H2("The idea: make a Canadian carrier your VoIP home base.")
+ P("Not a side entity or a backup &mdash; your actual home base. You set up one CRTC-registered Canadian carrier, put your customers on it <strong>from anywhere</strong> (US, Canada, or international), and your nexus &mdash; the carrier of record, your billing, banking, contracts, and regulatory home &mdash; sits in Canada.")
+ bq("Your Canadian entity files with the CRTC, not the FCC. For the traffic that lives in your Canadian jurisdiction there is no 499, no USAC contribution on your revenue, and no separate STIR/SHAKEN program for you to build. Where you serve US numbers, those US obligations are carried by the compliant US wholesale carriers you buy from &mdash; the same upstream model most small carriers already use.")
+ H2("What that means in practice.")
+ UL(
"<strong>A CRTC regulatory home, not an FCC one.</strong> Your Canadian carrier files with the CRTC. It is not an FCC-registered US carrier, so for its Canadian-jurisdiction traffic there is no 499-A, no 499-Q, and no separate RMD recertification on your side",
"<strong>No USAC/USF on your Canadian revenue.</strong> You don&rsquo;t register with USAC or remit the 38.8% contribution on your Canadian entity&rsquo;s revenue. For US-terminating traffic the contribution rides on your US wholesale upstream, exactly as it does for any reseller",
"<strong>STIR/SHAKEN stays handled &mdash; just not by you.</strong> A reseller can&rsquo;t be issued a US signing token, so the upstream carrier that owns the numbers signs the calls. Your calls stay properly attested; you simply don&rsquo;t stand up and run your own signing platform. (We don&rsquo;t help anyone dodge call-authentication &mdash; your upstream partners are fully STIR/SHAKEN compliant.)",
"<strong>Lighter lawful-intercept and KYC posture.</strong> CALEA-style build-out and the FCC&rsquo;s government-photo-ID mandate apply to US carriers; your Canadian entity follows Canada&rsquo;s lighter equivalents. You still know your customers &mdash; you just aren&rsquo;t under the US-specific photo-ID rule",
"<strong>No FCC Section 214, no ongoing 214 burden.</strong> Where the FCC requires an international Section 214 authorization (with Team Telecom review and continuing obligations), the CRTC equivalent &mdash; a BITS registration &mdash; is a simple, low-cost notification with no ongoing 214-style burden",
"<strong>Customers from anywhere.</strong> Onboard US, Canadian, or international customers onto one Canadian carrier &mdash; same +1 dialing, nothing changes on their end",
"<strong>US numbers still work.</strong> Several Canadian wholesale carriers provision <strong>US DIDs</strong> to CRTC-registered carriers through compliant US upstreams, so you can serve US customers directly &mdash; the free guide below lists which ones",
"<strong>One clean jurisdiction.</strong> Your carrier of record, banking, and contracts all sit in Canada, under the CRTC",
)
+ H2("&ldquo;How do I terminate to the US then?&rdquo;")
+ P("Routinely. Many US-based long-distance termination operators and wholesale carriers actively accept traffic from Canadian carriers &mdash; cross-border voice is one of the most common interconnects there is. You buy US numbers and US termination from a compliant wholesale partner, and they handle the US-side STIR/SHAKEN signing and US obligations on the way out &mdash; exactly how the vast majority of small carriers already rely on an upstream provider. Your Canadian-origin traffic falls under the CRTC&rsquo;s lighter regime, handled by your Canadian trunking provider.")
+ P("The point isn&rsquo;t to escape the rules &mdash; it&rsquo;s to put your regulatory home in Canada and let compliant upstream partners carry the US-specific stack (USF, the 499s, the RMD, signing certificates), instead of standing all of it up yourself.")
+ H2("One honest caveat: this is for real conversational voice.")
+ P("Canada is <strong>not</strong> the place for short-duration or high-volume dialer traffic. Canadian wholesale carriers are <strong>more stringent about call quality than just about anywhere else</strong> &mdash; they watch ASR and average call duration closely, and short-duration / low-ACD traffic gets flagged and cut off fast. If your business is real, longer-duration conversational voice &mdash; UCaaS, hosted PBX, business lines, residential, contact-center with live agents &mdash; this fits. If you&rsquo;re running short-duration dialer campaigns, a Canadian carrier is the wrong tool and we&rsquo;ll tell you so.")
+ H2("What we set up &mdash; turnkey, in 6&ndash;10 weeks.")
+ UL(
"Incorporation in <strong>British Columbia or Ontario</strong> &mdash; your Canadian carrier entity",
"<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("Start your Canadian carrier setup &mdash; $200 off &rarr;", ORDER_URL)
+ guide_block()
+ PS(f"Questions about moving your VoIP onto a Canadian carrier? {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% \u2014 move your VoIP home to Canada ($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>Move your VoIP home to Canada.",
"One CRTC carrier, nexus in Canada, customers from anywhere",
),
flagbar(
"US carrier \u2014 38.8% USF + the full FCC stack",
"Canadian carrier \u2014 CRTC home, US obligations ride on your upstream",
),
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())