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:
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue