#!/usr/bin/env python3 """ campaign_helpers.py — Shared Listmonk campaign building blocks. Branded, email-client-safe HTML helpers (header, flag bar, stat tiles, CTA button, footer, paragraphs, lists) plus the Listmonk POST helper. Extracted from create_campaigns.py so multiple campaign scripts (the original 7-email drips and one-off promos like the CRTC USF campaign) can share ONE source of truth for the look-and-feel and the API plumbing. Importing this module has NO side effects (it does not POST anything). """ import sys import requests # ── Listmonk connection ────────────────────────────────────────────────────── LISTMONK_URL = "https://lists.performancewest.net" AUTH = ("api", "6X1rKPea61N4rZ1S65Hx5zvqzbCj30F6nvEe9oVGH_Y") # ── Brand constants ────────────────────────────────────────────────────────── URL = "https://performancewest.net/order/canada-crtc" PHONE = "1-888-411-0383" EMAIL = "info@performancewest.net" CONTACT = ( f'Email {EMAIL} ' f'or call {PHONE}' ) # ── HTML helpers ───────────────────────────────────────────────────────────── def hdr(eyebrow, headline, sub=None): s = f'

{sub}

' if sub else '' return ( '
' '
' '' '' '' '
Performance WestTelecom Services
' '
 
' '
' f'

{eyebrow}

' f'

{headline}

{s}' '
' ) def flagbar(left, right): return ( '
' '' '' '' '' '
USA' f'{left}Canada' f'{right}
' ) def stats(*items): w = 100 // len(items) cells = "".join( f'' f'
' f'
{v}
' f'
{l}
' '
' for v, l in items ) return f'{cells}
' def cta(text, url): return ( '
' '' f'' '
{text}
' ) def carriers_block(): data = [ ("Twilio","NASDAQ: TWLO"),("Bandwidth","NASDAQ: BAND"),("Telnyx","Washington DC"),("RingCentral","NYSE: RNG"), ("Vonage","Holmdel NJ"),("8x8","NASDAQ: EGHT"),("Zoom Phone","NASDAQ: ZM"),("Dialpad","San Ramon CA"), ("Google Voice","Alphabet / GOOGL"),("Ooma","NYSE: OOMA"),("Onvoy / Sinch","OMX: SINCH"),("Sangoma","NASDAQ: SANG"), ] rows = "" for i in range(0, len(data), 4): cells = "".join( f'' f'
' f'
{n}
' f'
{t}
' '
' for n, t in data[i:i+4] ) rows += f"{cells}" return ( '' '
' '

' 'Join other US voice carriers registered to do business in Canada ' 'Canada

' f'{rows}
' '
' ) def ftr(note=""): note_html = f'{note}
' if note else '' return ( '' '
' 'Performance West' '

' 'Performance West Inc.  ·  performancewest.net

' f'

{note_html}' 'Unsubscribe

' '
' ) def bq(t): return ( '' '
{t}
' ) def P(t): return f'

{t}

' def PS(t): return f'

{t}

' def H2(t): return f'

{t}

' def UL(*items): return ( '' ) def assemble(hdr_html, fb_html, body_html, ftr_html): inner = ( hdr_html + fb_html + f'
{body_html}
' + ftr_html ) return ( '' '
' '' f'
{inner}
' ) def create_campaign(name, subject, lists, body_html, altbody=None, status="draft"): """POST a campaign to Listmonk. altbody: optional plaintext alternative. Strongly recommended for deliverability — an HTML-only campaign is a spam signal. Pass the output of scripts._email_plaintext.html_to_text(body_html) (or a hand-written plaintext). When omitted, Listmonk generates its own plaintext at send time. """ s = requests.Session() s.auth = AUTH payload = { "name": name, "subject": subject, "lists": lists, "type": "regular", "content_type": "html", "body": body_html, "status": status, } if altbody is not None: payload["altbody"] = altbody 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', '?') print(f" [{cid}] {name}") return cid