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:
parent
60d2572f19
commit
e3f439221a
1 changed files with 23 additions and 7 deletions
|
|
@ -859,7 +859,21 @@ def listmonk_sendable(email: str) -> tuple[bool, str]:
|
||||||
|
|
||||||
|
|
||||||
def get_base_campaign(campaign_id: int) -> dict:
|
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:
|
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.company }}", name or "Sample Carrier LLC")
|
||||||
body = body.replace("{{ .Subscriber.Attribs.dot_number }}", dot or "0000000")
|
body = body.replace("{{ .Subscriber.Attribs.dot_number }}", dot or "0000000")
|
||||||
body = body.replace("{{ .Subscriber.Attribs.state }}", state or "TX")
|
body = body.replace("{{ .Subscriber.Attribs.state }}", state or "TX")
|
||||||
# Real subscribers get a populated lp_link attrib; the test send must mirror
|
# Real subscribers get a populated lp_link attrib that already carries a
|
||||||
# that or the CTA button (e.g. "Check My Emissions Status") renders as a bare
|
# leading `?` query (the carrier's `?dot=`, plus `?code=` when a coupon is
|
||||||
# "?dot=..." that links to nowhere. Build the same link the audience gets,
|
# on). The test send MUST mirror that: a bare path here would render the CTA
|
||||||
# using the target_state (the state the offer applies to, which for per-state
|
# as `/order/slug&utm_source=...` (the template appends `&utm...`) which has
|
||||||
# programs comes from the deficiency flag, not the base state).
|
# 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 }}",
|
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,
|
# NOTE: leave {{ UnsubscribeURL }} alone — Listmonk renders it into a real,
|
||||||
# working per-subscriber unsubscribe link (even on test sends). Overwriting it
|
# working per-subscriber unsubscribe link (even on test sends). Overwriting it
|
||||||
# produced a dead /unsubscribe link with no subscriber identity.
|
# produced a dead /unsubscribe link with no subscriber identity.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue