Commit Graph

4 Commits

Author SHA1 Message Date
louispaulb
e7b937e2a3 style(campaigns/reminder): add "pour te remercier" / "as a thank-you"
Surfaces the WHY of the gift in the reminder body. The original
campaign was sent ~10 days before the reminder fires — recipients
may have forgotten the loyalty/gratitude context, leading to a
"what is this?" reaction when they see the reminder cold.

Adding two words ("pour te remercier" / "as a thank-you") cheaply
reconnects with the original messaging and reinforces TARGO's
relationship framing.

FR: "La carte-cadeau qu'on t'a envoyée pour te remercier peut
     s'utiliser chez des centaines de marques canadiennes..."

EN: "The gift card we sent you as a thank-you can be redeemed at
     hundreds of Canadian brands..."

Greeting kept as "Petit rappel pour {{firstname}}," — first-name
personalization beats generic "toi" on engagement metrics, and the
firstname auto-clean covers ~99% of recipients.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 15:09:41 -04:00
louispaulb
31562f62bf style(campaigns/templates): replace em-dashes with periods/commas
Some readers (and several modern style guides) read em-dashes as
"AI-written" feel — the user preferred a mix of period (for full
clauses) and comma (for asides) to keep the copy conversational
without the long pause em-dashes impose.

Period when both sides are independent clauses:
- about fiber. They're about people too. (EN main + reminder)
- on Giftbit. Just click your X. (EN main + reminder)
- pas manqué. La carte-cadeau qu'on t'a envoyée… (FR reminder)
- didn't miss it. The gift card… (EN reminder)

Comma when the second half is an aside or starts with "and":
- something special, for a limited time. (EN main)
- right next door, and we genuinely love… (EN main)
- aucun souci, pas besoin… (FR reminder)
- no worries, no need to reply… (EN reminder)

gift-email-fr unchanged — its user-visible text never had em-dashes
(the 3 detected were inside HTML comments).

No hub restart needed: the send worker reads templates fresh from
disk on every campaign run, so the new copy applies on the very next
"Lancer l'envoi" click.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 12:26:59 -04:00
louispaulb
73c42d6997 fix(campaigns/reminder): softer tone + render expiry in tests
The reminder copy read as pushy on test sends ("Hâte-toi! ... Tu n'as
encore rien fait, et le délai approche"). Toned down to factual and
friendly: state availability + offer the no-pressure path.

FR before / after:
   Hâte-toi! Ton cadeau de 60 $ expire le ___.       (red bold)
  → 🎁 Ton cadeau de 60 $ reste disponible jusqu'au 1 juillet 2026.
                                                       (brand dark green)

  Tu n'as encore rien fait, et le délai approche. Si tu n'utilises
  pas ton cadeau d'ici là, il ne pourra plus être réclamé.
  → On voulait juste s'assurer que tu ne l'as pas manqué — la carte-
  cadeau qu'on t'a envoyée peut s'utiliser chez des centaines de
  marques canadiennes, en quelques clics.
  Si tu préfères ne pas l'utiliser, aucun souci — pas besoin de
  répondre à ce courriel.

EN copy mirrored.

Also: {{expires_at_date}} was rendering empty in test sends and
previews because neither the test-send endpoint, the preview
endpoint, nor the editor's testSendForm.vars seeded it. Three fixes:
- Hub preview endpoint: compute now+30d as default sample date.
- Hub test-send endpoint: same default + expose view_url='' so the
  Mustache section block collapses cleanly in internal tests.
- Editor test-send dialog: pre-fill expires_at_date (and expires_in_
  days) with the same now+30d value, plus expose both fields as
  editable inputs so the operator can override per-test.

Verified live on prod: the preview endpoint with no vars now renders
"Ton cadeau de 60 $ reste disponible jusqu'au 1 juillet 2026."

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 11:55:20 -04:00
louispaulb
f414975b00 feat(campaigns): reminder campaign for non-clickers
Adds a "Créer une relance" button on the campaign detail page that
clones the parent campaign into a new draft, targeting only the
recipients who haven't clicked the Giftbit gift link yet.

Backend (POST /campaigns/:id/reminder):
- Filters parent recipients: status sent/opened, not excluded, not
  revoked, wrapper not yet expired, has a gift_url.
- Builds a fresh recipients array — same gift_url (Giftbit shortlink),
  same name/email/language/amount, but cleared gift_token so the worker
  generates a brand-new wrapper at send time. Each campaign owns its
  own click metrics.
- New campaign starts as 'draft' so the operator can review, tweak
  subject/template, and click "Lancer l'envoi" when ready.
- Tracks parent_campaign_id + parent_row_index on each reminder row
  for traceability in CSV reports and debugging.

Templates (gift-email-reminder-fr / gift-email-reminder-en):
- Header swap: "Petit rappel pour {firstname}" / "Quick reminder, X"
- Bold orange urgency line: " Hâte-toi! Ton cadeau de X expire le Y"
  using the existing {{expires_at_date}} and {{amount}} merge vars
- Body shortened — drops the manifesto, focuses on "you have a gift,
  redeem before it's gone"
- Same CTA button + prorata disclaimer + signature + footer as the
  main templates so brand stays consistent.

UI:
- Button visible when campaign is sending/completed AND it's not
  itself a reminder AND there's ≥ 1 eligible non-clicker.
- Confirmation dialog spells out the mechanics: same Giftbit URLs,
  new wrapper tokens, reminder template, sample expiry date pulled
  from the campaign's first recipient with a gift_expires_at.
- On OK, redirects to the new campaign's detail page.

Click stats on the existing campaign (cmp-20260522-2d4605) verified
intact before+after deploy (109 opens, 15 generic clicks, 27 gift CTA
clicks) — saveCampaign persists per-event so the hub restart was a
no-op for accumulated data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 11:43:35 -04:00