Reefer (separate tank) = strongest claim; PTO (same engine/tank) flagged as murkier federally and often a state refund. Added a 'how to prove non-propulsion fuel' section: ELD/telematics PTO+idle hours, PTO hour meter, inline flow meter, documented state allocation %, pump separation.
284 lines
16 KiB
Python
284 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
create_trucking_fueltax_newsletter.py — One-off trucking NEWSLETTER hooked on the
|
|
FreightWaves story about Prime Inc. suing the IRS over reefer-diesel fuel tax.
|
|
|
|
Angle : industry news + a "keep your fuel records" call to action, because the
|
|
federal fuel-excise credit Prime is fighting for (diesel burned by a
|
|
refrigeration unit isn't used to propel a highway vehicle) is one small
|
|
carriers can claim too -- and records are what position a carrier to
|
|
recover money if a class action follows.
|
|
Links : (1) the FreightWaves article, (2) our free DOT compliance check tool.
|
|
BOTH are STATIC URLs, so @TrackLink is safe here (the @TrackLink 404 bug
|
|
was specific to PER-SUBSCRIBER hrefs like ?dot={{...}} where Listmonk
|
|
captured the URL before the token rendered). Static links register one
|
|
redirect for everyone -- exactly what we want for click counts.
|
|
Fine print: Performance West is a filing service, NOT a law firm or tax advisor.
|
|
|
|
Creates the campaign in Listmonk as a DRAFT. Sending is a separate, manual,
|
|
STOP-and-confirm step (deliverability-sensitive: review audience + timing first).
|
|
|
|
python3 scripts/workers/create_trucking_fueltax_newsletter.py --list <id>
|
|
python3 scripts/workers/create_trucking_fueltax_newsletter.py --list <id> --preview-only
|
|
"""
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
if _REPO not in sys.path:
|
|
sys.path.insert(0, _REPO)
|
|
|
|
from scripts._email_plaintext import html_to_text # noqa: E402
|
|
from scripts.workers.campaign_helpers import create_campaign # noqa: E402
|
|
|
|
# ── Links ────────────────────────────────────────────────────────────────────
|
|
ARTICLE_URL = (
|
|
"https://www.freightwaves.com/news/"
|
|
"prime-is-suing-the-irs-for-11-million-over-fuel-tax-it-paid-on-reefer-diesel-"
|
|
"the-same-credit-it-is-fighting-for-is-one-small-carriers-can-claim-too"
|
|
)
|
|
DOT_CHECK_URL = "https://performancewest.net/tools/dot-compliance-check"
|
|
SITE = "https://performancewest.net"
|
|
|
|
EMAIL = "info@performancewest.net"
|
|
PHONE_TEL = "+18884110383"
|
|
PHONE_DISPLAY = "(888) 411-0383"
|
|
|
|
# Listmonk click tracking: a link is only registered (and clicks recorded) when
|
|
# its href ends with the literal "@TrackLink" marker. We also append UTM params so
|
|
# Umami attributes the visit too. SAFE here because both target URLs are static.
|
|
UTM_BASE = "utm_source=listmonk&utm_medium=email&utm_campaign=trucking-fueltax-newsletter"
|
|
|
|
|
|
def _track(url: str, content: str) -> str:
|
|
sep = "&" if "?" in url else "?"
|
|
return f"{url}{sep}{UTM_BASE}&utm_content={content}@TrackLink"
|
|
|
|
|
|
# ── Trucking-branded HTML helpers (navy #1a2744 / orange #f97316), matching the
|
|
# proven trucking campaign chrome (campaign 179). ───────────────────────────
|
|
FONT = "Inter,system-ui,Arial,sans-serif"
|
|
|
|
|
|
def header(eyebrow, headline):
|
|
return (
|
|
'<tr><td style="background:#1a2744;padding:24px 28px;">'
|
|
f'<img src="{SITE}/images/logo.png" alt="Performance West" style="height:44px;margin-bottom:12px;display:block" />'
|
|
f'<p style="margin:0 0 6px;font-size:11px;color:#f97316;letter-spacing:2px;text-transform:uppercase;font-weight:700;font-family:{FONT};">{eyebrow}</p>'
|
|
f'<h1 style="color:#fff;margin:0;font-size:22px;font-weight:700;line-height:1.3;font-family:{FONT};">{headline}</h1>'
|
|
'</td></tr>'
|
|
)
|
|
|
|
|
|
def P(t):
|
|
return f'<p style="font-size:14px;line-height:1.7;margin:0 0 16px;color:#1f2937;font-family:{FONT};">{t}</p>'
|
|
|
|
|
|
def H2(t):
|
|
return f'<h2 style="font-size:16px;font-weight:700;color:#1e3a5f;margin:26px 0 12px;font-family:{FONT};">{t}</h2>'
|
|
|
|
|
|
def UL(*items):
|
|
lis = "".join(
|
|
f'<li style="margin-bottom:9px;line-height:1.6;">{i}</li>' for i in items
|
|
)
|
|
return (
|
|
f'<ul style="margin:0 0 18px;padding-left:20px;font-size:14px;color:#1f2937;font-family:{FONT};">{lis}</ul>'
|
|
)
|
|
|
|
|
|
def quote(t):
|
|
return (
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 20px;"><tr>'
|
|
'<td style="border-left:4px solid #f97316;background:#fff7ed;border-radius:0 8px 8px 0;padding:14px 18px;'
|
|
f'font-size:14px;line-height:1.7;color:#7c2d12;font-family:{FONT};">{t}</td></tr></table>'
|
|
)
|
|
|
|
|
|
def cta(text, url, color="#f97316"):
|
|
return (
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:4px 0 24px;"><tr>'
|
|
'<td style="text-align:center;padding:6px 0;">'
|
|
f'<a href="{url}" style="display:inline-block;padding:15px 42px;background:{color};color:#fff;'
|
|
f'font-weight:700;border-radius:8px;text-decoration:none;font-size:15px;font-family:{FONT};'
|
|
f'box-shadow:0 4px 12px rgba(249,115,22,0.35);">{text}</a>'
|
|
'</td></tr></table>'
|
|
)
|
|
|
|
|
|
def disclaimer():
|
|
return (
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:8px 0 24px;"><tr>'
|
|
'<td style="background:#f8fafc;border:1px solid #e5e7eb;border-radius:8px;padding:14px 18px;">'
|
|
f'<p style="margin:0;font-size:11px;line-height:1.6;color:#6b7280;font-family:{FONT};">'
|
|
'<strong style="color:#475569;">Please note:</strong> Performance West is a DOT compliance and '
|
|
'filing service — we are <strong>not a law firm and not a tax advisor</strong>, and nothing in '
|
|
'this email is legal or tax advice. This is industry news. Whether your reefer or PTO fuel qualifies for '
|
|
'a federal fuel-tax credit, and whether to join any lawsuit or class action, depends on your specific '
|
|
'situation — please talk to a qualified tax professional or attorney before filing a claim or '
|
|
'taking action.</p></td></tr></table>'
|
|
)
|
|
|
|
|
|
def dot_check_block():
|
|
return (
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:20px 0 8px;"><tr>'
|
|
'<td style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;padding:20px 22px;">'
|
|
f'<p style="margin:0 0 6px;font-size:15px;font-weight:700;color:#1e3a5f;font-family:{FONT};">'
|
|
'While you’re thinking about records — is your DOT compliance current?</p>'
|
|
f'<p style="margin:0 0 14px;font-size:13px;line-height:1.6;color:#334155;font-family:{FONT};">'
|
|
'Run our <strong>free DOT compliance check</strong>: enter your USDOT number and we’ll show you '
|
|
'what’s due — MCS-150 biennial update, UCR, BOC-3 and more. No charge, no obligation.</p>'
|
|
+ cta("Run my free DOT check →", _track(DOT_CHECK_URL, "dotcheck"), color="#1e40af")
|
|
+ '</td></tr></table>'
|
|
)
|
|
|
|
|
|
def footer():
|
|
return (
|
|
'<tr><td style="padding:18px 28px;background:#f8fafc;border-top:1px solid #e5e7eb;text-align:center;">'
|
|
f'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 12px;"><tr>'
|
|
'<td style="background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:12px;text-align:center;">'
|
|
f'<p style="margin:0;font-size:13px;color:#1e3a5f;font-weight:600;font-family:{FONT};">'
|
|
f'Questions? Reply to this email, write <a href="mailto:{EMAIL}" style="color:#1e40af;text-decoration:none;">{EMAIL}</a> '
|
|
f'or call <a href="tel:{PHONE_TEL}" style="color:#1e40af;text-decoration:none;">{PHONE_DISPLAY}</a>.</p>'
|
|
'</td></tr></table>'
|
|
f'<p style="margin:0;font-size:11px;color:#9ca3af;font-family:{FONT};">Performance West Inc. · '
|
|
'525 Randall Ave Ste 100-1195, Cheyenne, WY 82001 · '
|
|
f'<a href="{SITE}" style="color:#6b7280;">performancewest.net</a></p>'
|
|
'<p style="margin:6px 0 0;font-size:11px;color:#9ca3af;font-family:Inter,sans-serif;">'
|
|
'<a href="{{ UnsubscribeURL }}" style="color:#6b7280;">Unsubscribe</a></p>'
|
|
'</td></tr>'
|
|
)
|
|
|
|
|
|
def build_body():
|
|
inner = (
|
|
P("There’s a fuel-tax story moving through trucking right now that could mean money back in your "
|
|
"pocket — not just for reefer haulers, but for anyone whose diesel runs equipment that "
|
|
"doesn’t move the truck. We wanted to put it in front of you.")
|
|
+ quote(
|
|
"<strong>FreightWaves reports</strong> that Prime Inc. is suing the IRS for about "
|
|
"<strong>$11 million</strong> over federal excise tax it paid on diesel burned by its "
|
|
"<strong>refrigerated (reefer) trailer units</strong>. Prime’s argument: diesel used to run "
|
|
"a reefer unit isn’t used to propel a truck down the highway, so it shouldn’t carry the "
|
|
"federal on-highway fuel tax — and the same credit it’s fighting for is one that small "
|
|
"carriers can claim too."
|
|
)
|
|
+ H2("Reefer fuel is the clearest case — PTO may count too")
|
|
+ P("The federal excise tax on diesel is about <strong>24.3 cents a gallon</strong>, and it’s "
|
|
"meant to pay for diesel that <em>propels a vehicle down the highway</em>. The thing is, a lot of "
|
|
"the diesel you buy never turns a wheel:")
|
|
+ UL(
|
|
"<strong>Reefer units (the clearest case)</strong> — most refrigerated trailers run a "
|
|
"separate engine off a separate tank, so that fuel plainly isn’t propelling your truck. That "
|
|
"is exactly what Prime is fighting for, and it’s the strongest claim",
|
|
"<strong>Power take-off (PTO) & auxiliary equipment (often state-level)</strong> — "
|
|
"dump-truck hydraulics, cement mixers, bucket / boom / digger derricks, pumpers, blowers, refuse "
|
|
"packers, sweepers and wreckers usually run off the <em>same</em> engine and tank that moves the "
|
|
"truck. That fuel may still qualify, but it’s murkier federally and a lot of PTO recovery "
|
|
"actually happens through <strong>state</strong> fuel-tax refund programs — worth asking your "
|
|
"tax pro about",
|
|
)
|
|
+ P("Reefer fuel may be refundable on <strong>IRS Form 4136</strong> (or via Form 8849). For PTO and "
|
|
"other auxiliary use, ask your tax professional about both the federal off-highway credit and your "
|
|
"state’s fuel-tax refund — either way, the money only comes back if you can show how much "
|
|
"fuel went to non-propulsion use.")
|
|
+ P("Over a year of runs that adds up — and if a class action follows Prime’s lawsuit, the "
|
|
"carriers who kept clean records will be the ones positioned to claim their share.")
|
|
+ H2("The hard part: proving how much fuel was non-propulsion")
|
|
+ P("If your reefer has its own tank, this is easy — its fuel is already separate. The tricky case "
|
|
"is <strong>same-engine, same-tank PTO use</strong> (a dump truck or mixer burns from the same tank "
|
|
"whether it’s driving or working). You can’t claim what you can’t substantiate, so "
|
|
"here’s how carriers actually measure it:")
|
|
+ UL(
|
|
"<strong>Pull PTO / idle hours from your ELD or telematics</strong> — most modern trucks’ "
|
|
"engine computers (ECM) already log PTO-engine and idle time separately from drive time. Multiply "
|
|
"those hours by a fuel-burn rate (manufacturer spec or a measured gal/hr) to estimate gallons",
|
|
"<strong>Add a PTO / equipment hour meter</strong> — an inexpensive hour meter on the "
|
|
"PTO circuit gives you a clean, independent running total of equipment time",
|
|
"<strong>Install an inline fuel-flow meter</strong> on the reefer or PTO feed — the most "
|
|
"precise option (actual gallons, not an estimate) if the recovery is large enough to justify it",
|
|
"<strong>Use a documented allocation method</strong> — when metering isn’t practical, "
|
|
"many state programs publish PTO allowance percentages by equipment type (mixer, refuse packer, "
|
|
"etc.); a consistent, documented basis is far better than a guess",
|
|
"<strong>Separate the fuel at the pump where you can</strong> — a dedicated fuel card or "
|
|
"separate fill for the reefer / equipment tank turns the whole question into a simple receipt total",
|
|
)
|
|
+ H2("What to do now: gather and keep your fuel records")
|
|
+ P("You don’t need to do anything drastic today. The single most important thing is simple: "
|
|
"<strong>don’t throw your fuel records away</strong>, and start keeping them organized.")
|
|
+ UL(
|
|
"Save your <strong>fuel receipts and IFTA fuel summaries</strong> — especially anything that "
|
|
"separates reefer or PTO fuel from the fuel that moves the truck",
|
|
"Export and keep your <strong>ELD / telematics PTO and idle-hour reports</strong> — this is "
|
|
"the record most carriers already have and forget to save",
|
|
"Hold onto <strong>reefer run logs, PTO / engine hours, and equipment-meter readings</strong>",
|
|
"Keep everything <strong>by quarter and by year</strong> — the cleaner the paper trail, the "
|
|
"stronger any future claim",
|
|
)
|
|
+ P("If a class action or a clearer IRS position emerges, the carriers with documentation in hand will "
|
|
"be the ones ready to act.")
|
|
+ cta("Read the full story on FreightWaves →", _track(ARTICLE_URL, "article"))
|
|
+ disclaimer()
|
|
+ dot_check_block()
|
|
+ P("Drive safe out there,")
|
|
+ P("<strong>Justin Hannah</strong><br>Performance West Inc.<br>"
|
|
f'<span style="font-size:12px;color:#64748b;font-family:{FONT};">DOT Compliance Services</span>')
|
|
)
|
|
return (
|
|
'<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">'
|
|
'<meta name="viewport" content="width=device-width,initial-scale=1">'
|
|
'<style>@media only screen and (max-width:600px){.pw-wrap{width:100%!important;border-radius:0!important;}'
|
|
'.pw-pad{padding:24px 16px!important;}}body,table,td,p,a{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}'
|
|
'table{border-collapse:collapse!important;}img{border:0;outline:none;text-decoration:none;}</style></head>'
|
|
'<body style="margin:0;padding:0;background:#eef0f3;"><center>'
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#eef0f3;"><tr>'
|
|
'<td style="padding:24px 10px;">'
|
|
'<table role="presentation" class="pw-wrap" width="620" cellpadding="0" cellspacing="0" '
|
|
'style="margin:0 auto;border-radius:10px;overflow:hidden;background:#fff;">'
|
|
+ header("Industry News · Fuel Tax",
|
|
"Prime is suing the IRS for $11M over reefer fuel tax — "
|
|
"and the same credit may apply to your reefer or PTO fuel")
|
|
+ '<tr><td class="pw-pad" style="padding:28px;">' + inner + '</td></tr>'
|
|
+ footer()
|
|
+ '</table></td></tr></table></center></body></html>'
|
|
)
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--list", type=int, help="Listmonk list id to target (the newsletter audience)")
|
|
ap.add_argument("--name", default="Trucking Newsletter \u2014 Reefer/PTO Fuel-Tax Story + Keep Your Records")
|
|
ap.add_argument("--subject",
|
|
default="Are you owed a refund on the fuel tax you paid for your reefer or PTO?")
|
|
ap.add_argument("--preview-only", action="store_true",
|
|
help="write the rendered HTML to /tmp and do NOT touch Listmonk")
|
|
args = ap.parse_args()
|
|
|
|
body = build_body()
|
|
altbody = html_to_text(body)
|
|
|
|
out = "/tmp/trucking_fueltax_newsletter.html"
|
|
with open(out, "w") as f:
|
|
f.write(body)
|
|
print(f"Rendered preview -> {out}")
|
|
print(f"Body: {len(body):,} chars HTML / {len(altbody):,} chars plaintext")
|
|
|
|
if args.preview_only:
|
|
return 0
|
|
if not args.list:
|
|
print("\nNo --list given; created preview only. Re-run with --list <id> to create the draft.",
|
|
file=sys.stderr)
|
|
return 0
|
|
|
|
print(f"\n=== Creating trucking fuel-tax newsletter (DRAFT, list {args.list}) ===")
|
|
cid = create_campaign(args.name, args.subject, [args.list], body, altbody=altbody, status="draft")
|
|
if cid:
|
|
print(f"\nDraft created (id {cid}). Review/preview in Listmonk, then send manually.")
|
|
return 0 if cid else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|