fix(trucking-email): kill recurring @TrackLink 404 at the source-clone boundary

Root cause of the order-CTA 404s recurring after the prior live fix: the
builder clones email bodies from STORED Listmonk source campaigns (ids
186/188/271-274/309/310/469/473), not from the edited source files. Those
stored bodies still carried @TrackLink on the per-subscriber order CTA, so
every nightly build re-registered a single static /order/<slug>&utm... link
(no '?') that 404s for every recipient. This morning's 3,000 real sends AND
the owner spot-check both went out with dead order links.

Two durable guards:
1. get_base_campaign() now strips @TrackLink from any cloned body (with a
   warning), so a stale/re-edited source campaign can never reach recipients
   broken again. Human clicks are already attributed via Umami.
2. The owner test-send now builds the CTA via lp_link_with_coupon(dot=...)
   (leading '?') instead of build_lp_link() (bare path).

Also fixed live: stripped @TrackLink from the 10 stored source campaign
bodies; rewrote the 12 already-registered broken links. Backups in listmonk:
pw_source_tracklink_bak_20260623 + pw_links_tracklink_bak_20260623.
This commit is contained in:
justin 2026-06-23 15:02:05 -05:00
parent 60d2572f19
commit e3f439221a

View file

@ -859,7 +859,21 @@ def listmonk_sendable(email: str) -> tuple[bool, str]:
def get_base_campaign(campaign_id: int) -> dict:
return lm_api(f"/campaigns/{campaign_id}")["data"]
data = lm_api(f"/campaigns/{campaign_id}")["data"]
# Defensively strip Listmonk's @TrackLink marker from cloned bodies. Our CTAs
# are PER-SUBSCRIBER (`{{ lp_link }}`, `?dot=...`), and @TrackLink registers a
# SINGLE static URL per tracked link: it both 404s (the registered URL is
# captured with the `{{ lp_link }}` token unrendered -> `/order/slug&utm...`
# with no `?`) and collapses every recipient onto one carrier's redirect.
# Real human clicks are already attributed via Umami's campaign-click event,
# so dropping the marker loses nothing. This guard means a stale source
# campaign re-edited with @TrackLink can never reach recipients broken again.
body = data.get("body")
if body and "@TrackLink" in body:
data["body"] = body.replace("@TrackLink", "")
LOG.warning("[%s] stripped @TrackLink from cloned source body id=%s",
"get_base_campaign", campaign_id)
return data
def create_list(name: str) -> int:
@ -1001,13 +1015,15 @@ def send_test(base: dict, campaign_id: int, sample_row: tuple, label: str, tz: s
body = body.replace("{{ .Subscriber.Attribs.company }}", name or "Sample Carrier LLC")
body = body.replace("{{ .Subscriber.Attribs.dot_number }}", dot or "0000000")
body = body.replace("{{ .Subscriber.Attribs.state }}", state or "TX")
# Real subscribers get a populated lp_link attrib; the test send must mirror
# that or the CTA button (e.g. "Check My Emissions Status") renders as a bare
# "?dot=..." that links to nowhere. Build the same link the audience gets,
# using the target_state (the state the offer applies to, which for per-state
# programs comes from the deficiency flag, not the base state).
# Real subscribers get a populated lp_link attrib that already carries a
# leading `?` query (the carrier's `?dot=`, plus `?code=` when a coupon is
# on). The test send MUST mirror that: a bare path here would render the CTA
# as `/order/slug&utm_source=...` (the template appends `&utm...`) which has
# no `?` and 404s — exactly the broken owner-spot-check link. Use the same
# lp_link_with_coupon() the audience gets so the `?` is present.
body = body.replace("{{ .Subscriber.Attribs.lp_link }}",
build_lp_link(campaign_type, target_state))
lp_link_with_coupon(campaign_type, target_state, None,
dot=dot or "0000000"))
# NOTE: leave {{ UnsubscribeURL }} alone — Listmonk renders it into a real,
# working per-subscriber unsubscribe link (even on test sends). Overwriting it
# produced a dead /unsubscribe link with no subscriber identity.