From f414975b00b8ac5910027ebc231075bbe5421a7a Mon Sep 17 00:00:00 2001 From: louispaulb Date: Mon, 1 Jun 2026 11:43:35 -0400 Subject: [PATCH] feat(campaigns): reminder campaign for non-clickers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/ops/src/api/campaigns.js | 11 + .../campaigns/pages/CampaignDetailPage.vue | 75 +- .../templates/gift-email-reminder-en.html | 898 +++++++++++++++++ .../templates/gift-email-reminder-en.json | 98 ++ .../templates/gift-email-reminder-fr.html | 908 ++++++++++++++++++ .../templates/gift-email-reminder-fr.json | 98 ++ services/targo-hub/lib/campaigns.js | 77 ++ .../templates/gift-email-reminder-en.html | 898 +++++++++++++++++ .../templates/gift-email-reminder-en.json | 98 ++ .../templates/gift-email-reminder-fr.html | 908 ++++++++++++++++++ .../templates/gift-email-reminder-fr.json | 98 ++ 11 files changed, 4165 insertions(+), 2 deletions(-) create mode 100644 scripts/campaigns/templates/gift-email-reminder-en.html create mode 100644 scripts/campaigns/templates/gift-email-reminder-en.json create mode 100644 scripts/campaigns/templates/gift-email-reminder-fr.html create mode 100644 scripts/campaigns/templates/gift-email-reminder-fr.json create mode 100644 services/targo-hub/templates/gift-email-reminder-en.html create mode 100644 services/targo-hub/templates/gift-email-reminder-en.json create mode 100644 services/targo-hub/templates/gift-email-reminder-fr.html create mode 100644 services/targo-hub/templates/gift-email-reminder-fr.json diff --git a/apps/ops/src/api/campaigns.js b/apps/ops/src/api/campaigns.js index cc53535..eda10c3 100644 --- a/apps/ops/src/api/campaigns.js +++ b/apps/ops/src/api/campaigns.js @@ -104,6 +104,17 @@ export function retryRecipient (campaignId, rowIndex) { ) } +// Create a brand-new draft campaign targeted at the parent's non-clicked +// recipients. Uses gift-email-reminder-* templates + an urgency subject. +// The new campaign is returned but NOT auto-sent — the operator reviews +// and clicks Lancer l'envoi when ready. +export function createReminderCampaign (parentCampaignId) { + return hubFetch( + `/campaigns/${encodeURIComponent(parentCampaignId)}/reminder`, + { method: 'POST' }, + ) +} + // Build the URL the browser hits to download the per-recipient CSV report // (giftbit shortlink ↔ email ↔ name ↔ status). The hub serves it with the // proper Content-Disposition so an click triggers a save. diff --git a/apps/ops/src/modules/campaigns/pages/CampaignDetailPage.vue b/apps/ops/src/modules/campaigns/pages/CampaignDetailPage.vue index 4140213..79e236c 100644 --- a/apps/ops/src/modules/campaigns/pages/CampaignDetailPage.vue +++ b/apps/ops/src/modules/campaigns/pages/CampaignDetailPage.vue @@ -12,6 +12,11 @@ > Télécharger le rapport (shortlinks Giftbit, emails, statuts d'envoi) + + Cloner cette campagne pour les destinataires qui n'ont PAS cliqué le cadeau encore + @@ -125,16 +130,18 @@