#!/usr/bin/env python3 """Create non-MCS-150 trucking Listmonk campaigns and send owner tests. Creates draft campaigns only. It does not schedule or start any bulk send. Usage: python3 scripts/setup_trucking_campaigns.py LISTMONK_URL=https://lists.performancewest.net CAMPAIGN_TEST_EMAIL=carrierone@gmx.com python3 scripts/setup_trucking_campaigns.py """ from __future__ import annotations import base64 import json import os import urllib.error import urllib.parse import urllib.request LISTMONK_URL = os.getenv("LISTMONK_URL", "https://lists.performancewest.net").rstrip("/") LISTMONK_USER = os.getenv("LISTMONK_USER", "api") LISTMONK_PASS = os.getenv("LISTMONK_PASS", "6X1rKPea61N4rZ1S65Hx5zvqzbCj30F6nvEe9oVGH_Y") TEST_EMAIL = os.getenv("CAMPAIGN_TEST_EMAIL", "carrierone@gmx.com") FROM_EMAIL = "Performance West " REPLY_TO = "info@performancewest.net" AUTH = base64.b64encode(f"{LISTMONK_USER}:{LISTMONK_PASS}".encode()).decode() def api(path: str, data: dict | None = None, method: str | None = None) -> dict: headers = { "Authorization": f"Basic {AUTH}", "Content-Type": "application/json", "Accept": "application/json", } req = urllib.request.Request(f"{LISTMONK_URL}/api{path}", headers=headers) if data is not None: req.data = json.dumps(data).encode() if method: req.method = method try: raw = urllib.request.urlopen(req, timeout=45).read() return json.loads(raw or b"{}") except urllib.error.HTTPError as exc: body = exc.read().decode(errors="replace")[:1000] raise RuntimeError(f"Listmonk {path} HTTP {exc.code}: {body}") from exc def list_map() -> dict[str, dict]: resp = api("/lists?per_page=all") results = resp.get("data", {}).get("results", []) return {row["name"]: row for row in results} def get_or_create_list(name: str, tags: list[str]) -> int: lists = list_map() if name in lists: return int(lists[name]["id"]) resp = api("/lists", {"name": name, "type": "private", "optin": "single", "tags": tags}, "POST") return int(resp["data"]["id"]) def service_rows(rows: list[tuple[str, str]]) -> str: body = "".join( f'{name}' f'{price}' for name, price in rows ) return f'{body}
' def requirement_rows(rows: list[str]) -> str: cells = "".join( f'{row}' for row in rows[:-1] ) cells += f'{rows[-1]}' return f'{cells}
' def build_email(cfg: dict) -> str: cta_url = cfg["cta_url"] cta = f"{cta_url}{'&' if '?' in cta_url else '?'}utm_source=listmonk&utm_medium=email&utm_campaign={cfg['slug']}" return f'''
''' CAMPAIGNS = [ { "slug": "ca-mcp-carb", "list": "FMCSA State - California (MCP/CARB)", "name": "CA Motor Carrier Permit + CARB Compliance Alert", "headline": "California Compliance Alert", "subject": "California MCP + CARB requirements for {{ .Subscriber.Attribs.company }}", "requirements": [ "Motor Carrier Permit (MCP) — California requires many for-hire carriers and qualifying CMVs to maintain an MCP through CA DMV.", "CA Number / CHP requirements — California carriers may need a state carrier number in addition to USDOT.", "CARB emissions compliance — diesel vehicles can face California emissions restrictions and enforcement.", ], "services": [("California MCP + CARB Compliance", "$349"), ("IRP Registration Assistance", "$199"), ("IFTA Application + Decals", "$149"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/california/", "cta_text": "View California Compliance Services", }, { "slug": "or-weight-mile", "list": "FMCSA State - Oregon (Weight-Mile Tax)", "name": "Oregon Weight-Mile Tax Compliance Alert", "headline": "Oregon Weight-Mile Alert", "subject": "Oregon Weight-Mile Tax registration for {{ .Subscriber.Attribs.company }}", "requirements": [ "Weight-Mile Tax — Oregon trucks over 26,000 lbs generally need weight-mile tax registration and filings.", "IRP / apportioned plates — interstate Oregon operations often require apportioned registration.", "ODOT authority — some for-hire intrastate operations require state authority.", ], "services": [("Oregon Weight-Mile Tax Setup", "$199"), ("IRP Registration Assistance", "$199"), ("IFTA Application + Decals", "$149"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/oregon/", "cta_text": "View Oregon Compliance Services", }, { "slug": "ny-hut", "list": "FMCSA State - New York (HUT)", "name": "NY Highway Use Tax Compliance Alert", "headline": "New York HUT Alert", "subject": "New York HUT registration for {{ .Subscriber.Attribs.company }}", "requirements": [ "Highway Use Tax (HUT) — New York requires HUT registration for many vehicles over 18,000 lbs.", "Quarterly filings — HUT is not just a one-time registration. Reporting may be recurring.", "Intrastate authority — for-hire NY intrastate carriers may need NY authority.", ], "services": [("NY Highway Use Tax Registration", "$199"), ("HUT Quarterly Filing", "$99"), ("Intrastate Operating Authority", "$249"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/new-york/", "cta_text": "View New York Compliance Services", }, { "slug": "ky-kyu", "list": "FMCSA State - Kentucky (KYU)", "name": "Kentucky KYU Weight-Distance Tax Alert", "headline": "Kentucky KYU Alert", "subject": "Kentucky KYU number requirements for {{ .Subscriber.Attribs.company }}", "requirements": [ "KYU number — Kentucky requires weight-distance tax registration for vehicles over 59,999 lbs combined license weight.", "Mileage reporting — KYU tax is based on miles operated in Kentucky and requires ongoing filings.", "Permit exposure — operating without proper KYU setup can create roadside and tax issues.", ], "services": [("Kentucky KYU Number Setup", "$199"), ("KYU Quarterly Filing", "$99"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/kentucky/", "cta_text": "View Kentucky Compliance Services", }, { "slug": "nm-weight-distance", "list": "FMCSA State - New Mexico (Weight-Distance)", "name": "New Mexico Weight-Distance Tax Alert", "headline": "New Mexico Weight-Distance Alert", "subject": "New Mexico Weight-Distance Tax for {{ .Subscriber.Attribs.company }}", "requirements": [ "Weight-Distance Tax — New Mexico requires qualifying commercial vehicles to register and file weight-distance returns.", "Trip and mileage records — filings depend on New Mexico miles and vehicle weight class.", "Operating state gap — carriers passing through NM can trigger requirements even if based elsewhere.", ], "services": [("New Mexico Weight-Distance Setup", "$199"), ("Quarterly Filing", "$99"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/new-mexico/", "cta_text": "View New Mexico Compliance Services", }, { "slug": "ct-huf", "list": "FMCSA State - Connecticut (Highway Use Fee)", "name": "Connecticut Highway Use Fee Alert", "headline": "Connecticut Highway Use Fee Alert", "subject": "Connecticut Highway Use Fee for {{ .Subscriber.Attribs.company }}", "requirements": [ "Highway Use Fee — Connecticut applies a per-mile highway use fee to certain heavy vehicles over 26,000 lbs.", "Quarterly filing — HUF returns are recurring and based on mileage and weight class.", "Multi-state exposure — CT trips can trigger filing duties even for carriers based outside Connecticut.", ], "services": [("Connecticut HUF Registration", "$199"), ("Quarterly Filing", "$99"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/connecticut/", "cta_text": "View Connecticut Compliance Services", }, { "slug": "tx-txdmv", "list": "FMCSA State - Texas (TxDMV)", "name": "Texas TxDMV Number / Intrastate Authority Alert", "headline": "Texas TxDMV Alert", "subject": "Texas TxDMV number requirements for {{ .Subscriber.Attribs.company }}", "requirements": [ "TxDMV number — many Texas intrastate motor carriers need a TxDMV number in addition to USDOT.", "Intrastate operating authority — for-hire Texas operations can trigger state authority and insurance filing requirements.", "Insurance filing — Texas authority often requires proof of insurance on file with the state.", ], "services": [("Texas TxDMV Number Filing", "$249"), ("Texas Intrastate Authority", "$249"), ("State Insurance Filing", "$99"), ("State Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/texas/", "cta_text": "View Texas Compliance Services", }, { "slug": "irp-ifta-starter", "list": "FMCSA Trucking - IRP IFTA Prospects", "name": "IRP / IFTA Interstate Carrier Starter Campaign", "headline": "IRP / IFTA Filing Alert", "subject": "Crossing state lines? IRP and IFTA checklist for {{ .Subscriber.Attribs.company }}", "requirements": [ "IRP apportioned registration — qualifying interstate vehicles generally need apportioned plates through their base state.", "IFTA fuel tax account — interstate fuel tax registration and decals may be required before operating across state lines.", "Quarterly IFTA returns — IFTA continues after registration with quarterly fuel tax reporting.", ], "services": [("IRP Registration Assistance", "$199"), ("IFTA Application + Decals", "$149"), ("Quarterly IFTA Filing", "$99"), ("IRP/IFTA Bundle", "$299")], "cta_url": "https://performancewest.net/services/trucking/irp-ifta/", "cta_text": "Get IRP / IFTA Help", }, { "slug": "dot-drug-alcohol", "list": "FMCSA Trucking - Drug Alcohol Prospects", "name": "DOT Drug & Alcohol Program Campaign", "headline": "DOT Drug & Alcohol Alert", "subject": "DOT drug and alcohol testing requirements for {{ .Subscriber.Attribs.company }}", "requirements": [ "CDL driver testing — carriers with CDL drivers generally need a DOT-compliant drug and alcohol testing program.", "Random consortium — owner-operators usually need to be enrolled in a compliant random testing pool.", "Audit exposure — missing testing records are common issues during safety audits and investigations.", ], "services": [("DOT Drug & Alcohol Program", "$149/yr"), ("Clearinghouse / Driver File Review", "$99"), ("DOT Full Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/drug-alcohol-program/", "cta_text": "Enroll in DOT D&A Program", }, { "slug": "hazmat-compliance", "list": "FMCSA Trucking - Hazmat Prospects", "name": "Hazmat Carrier Compliance Campaign", "headline": "Hazmat Compliance Alert", "subject": "Hazmat compliance review for {{ .Subscriber.Attribs.company }}", "requirements": [ "Hazmat registration — carriers transporting regulated hazardous materials may need PHMSA/HM registration.", "Higher insurance and safety exposure — hazmat operations often trigger stricter insurance and audit requirements.", "Driver and security records — training, endorsements, and emergency response documents need to be audit-ready.", ], "services": [("Hazmat Compliance Review", "$199"), ("Safety Audit Prep", "$299"), ("Insurance / State Filing Review", "$99"), ("DOT Full Compliance Bundle", "$599")], "cta_url": "https://performancewest.net/services/trucking/hazmat/", "cta_text": "Review Hazmat Compliance", }, { "slug": "new-carrier-startup", "list": "FMCSA Trucking - New Carrier Missing Startup Items", "name": "New Carrier Startup Checklist Campaign", "headline": "New Carrier Startup Checklist", "subject": "New DOT number? Startup checklist for {{ .Subscriber.Attribs.company }}", "requirements": [ "Our startup checklist flagged these likely missing items for your carrier:
{{ Safe .Subscriber.Attribs.missing_items_html }}", "Startup filing sequence — LLC, EIN, USDOT, operating authority, BOC-3, UCR, D&A, insurance, IRP and IFTA can all stack up quickly.", "Missed renewals — new carriers often miss UCR, MCS-150 timing, state permits, or quarterly fuel tax obligations.", "Cash-flow friendly setup — bundling startup compliance prevents piecemeal filings and duplicate work.", ], "services": [("New Carrier Starter Bundle", "$599"), ("LLC Formation", "$199"), ("BOC-3", "$49"), ("UCR Filing", "$99")], "cta_url": "https://performancewest.net/services/trucking/new-carrier/", "cta_text": "Start Carrier Compliance", }, ] def create_campaign(cfg: dict, list_id: int) -> int: payload = { "name": cfg["name"], "subject": cfg["subject"], "from_email": FROM_EMAIL, "type": "regular", "content_type": "html", "body": build_email(cfg), "lists": [list_id], "template_id": 1, "headers": [{"Reply-To": REPLY_TO}], "tags": ["dot-compliance", "trucking", cfg["slug"]], "messenger": "email", } resp = api("/campaigns", payload, "POST") return int(resp["data"]["id"]) def send_test(campaign_id: int, cfg: dict, list_id: int) -> None: body = build_email(cfg) sample = { "name": cfg["name"], "subject": "[TEST] " + cfg["subject"].replace("{{ .Subscriber.Attribs.company }}", "Sample Carrier LLC"), "from_email": FROM_EMAIL, "type": "regular", "content_type": "html", "body": body, "lists": [list_id], "template_id": 1, "headers": [{"Reply-To": REPLY_TO}], "tags": ["dot-compliance", "trucking", cfg["slug"], "test"], "messenger": "email", "subscribers": [TEST_EMAIL], } api(f"/campaigns/{campaign_id}/test", sample, "POST") def main() -> None: print(f"Listmonk: {LISTMONK_URL}") print(f"Sending tests to: {TEST_EMAIL}") created: list[tuple[str, int, int]] = [] for cfg in CAMPAIGNS: list_id = get_or_create_list(cfg["list"], ["trucking", "dot-compliance", cfg["slug"]]) campaign_id = create_campaign(cfg, list_id) send_test(campaign_id, cfg, list_id) created.append((cfg["name"], campaign_id, list_id)) print(f"created draft campaign {campaign_id} on list {list_id}: {cfg['name']} | test sent") print("\nSummary:") for name, campaign_id, list_id in created: print(f"- #{campaign_id} list #{list_id}: {name}") if __name__ == "__main__": main()