feat(campaigns): apply real TARGO brand + auto-route FR/EN by Customer.language

Brand audit against the official guide (Feb 2026 v1.0) caught several
inconsistencies in the email template:

- Wrong primary green: was #019547, should be #00C853 (Targo Green from
  brand palette). Globally replaced.
- Wrong gradient: was #019547→#06a04d, should be 135deg #00C853→#005026
  (the official Gradient Targo from the brand). Now using Outlook-safe
  background-image + bgcolor fallback for solid green on Outlook desktop.
- Wrong contact info: facturation@targointernet.com / 514 242-1500 →
  support@targo.ca / 514 448-0773 / 1 855 888-2746 (per §11 of guide).
- Wrong website: targointernet.com + gigafibre.ca → www.targo.ca.
- Missing slogan + green dot: footer now ends with the trademark
  tagline "Services de confiance, tout-en-un, près de chez vous." with
  the obligatory green period (always FR — it's the trademark, not a
  marketing line, so stays untranslated in EN template too).
- Missing brand fonts: added Space Grotesk (display) + Plus Jakarta
  Sans (body) via Google Fonts. Wrapped in MSO conditional comments so
  Outlook desktop skips the request and falls back to Helvetica via
  the explicit font-family stack on every element.
- Wrong body bg / text colors: now #F5FAF7 (Muted) / #1B2E24
  (Foreground) per brand semantic palette.
- Wrong info-pill bg: was #f3f4f3 → #F5FAF7 (Muted).
- Added official dark footer band #1C1E26 (Targo Dark) with white
  inverted wordmark, slogan, address, copyright.

Multilang routing (FR/EN):

- lib/campaigns.js matchCustomer now fetches Customer.language
  (14k FR / 1k EN distribution confirmed on prod). Default 'fr' for
  unmatched contacts.
- New templateForLanguage(lang) helper picks gift-email-<lang>.html,
  falls back to FR. Resolves 'fr-CA' → 'fr' etc.
- sendCampaignAsync pre-loads templates per recipient with an in-memory
  cache to avoid re-reading from disk on every send.
- gift-email-en.html created — English translation of the full FR
  template, keeping the slogan in French (it's the trademark tagline).
- year variable now injected (replaces hardcoded © year).

UI (CampaignNewPage):

- New "Langue" column in the Step 2 recipient table. Shows a clickable
  chip (FR primary green / EN blue-grey) that toggles language inline,
  so a campaign manager can override the ERPNext-resolved language
  per recipient.
- Step 3 recap now shows "Répartition par langue: 145 × FR, 12 × EN"
  before confirming the send.

Spell-check:

- TemplateEditorPage HTML mode now has spellcheck="true" + dynamic
  lang attribute on the textarea, picked from the template name suffix
  (gift-email-fr → fr, gift-email-en → en). Browser's native dictionary
  flags typos in real time. AI-grade rewrites deferred to the future
  /campaigns/ai/rewrite endpoint discussed previously.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-05-21 20:50:56 -04:00
parent 8d9e190c21
commit d6096fe1f8
7 changed files with 914 additions and 72 deletions

View File

@ -150,6 +150,21 @@
</a>
</q-td>
</template>
<template v-slot:body-cell-language="props">
<q-td :props="props">
<!-- Clickable chip toggles FR EN inline. Default value comes
from the matched Customer.language (or 'fr' for unmatched).
The send-worker picks the template by this value at send
time, so flipping here changes which template gets used. -->
<q-chip dense clickable size="sm"
:color="props.row.language === 'en' ? 'blue-grey-6' : 'primary'"
text-color="white"
:label="(props.row.language || 'fr').toUpperCase()"
@click="props.row.language = props.row.language === 'en' ? 'fr' : 'en'">
<q-tooltip>Cliquer pour basculer FR EN</q-tooltip>
</q-chip>
</q-td>
</template>
<template v-slot:body-cell-match="props">
<q-td :props="props">
<q-chip v-if="props.row.customer_id" dense color="positive" text-color="white" size="sm" :label="props.row.match_method" />
@ -212,6 +227,7 @@
<q-list dense>
<q-item><q-item-section side>Nom</q-item-section><q-item-section>{{ params.name }}</q-item-section></q-item>
<q-item><q-item-section side>Destinataires actifs</q-item-section><q-item-section>{{ sendableCount }} (sur {{ recipients.length }} paires ; {{ excludedCount }} exclus, {{ unpairedContacts.length }} sans lien)</q-item-section></q-item>
<q-item><q-item-section side>Répartition par langue</q-item-section><q-item-section>{{ langBreakdown }}</q-item-section></q-item>
<q-item><q-item-section side>Montant affiché</q-item-section><q-item-section>{{ params.amount }}</q-item-section></q-item>
<q-item><q-item-section side>Engagement</q-item-section><q-item-section>{{ params.commitment_months }} mois</q-item-section></q-item>
<q-item><q-item-section side>Sujet</q-item-section><q-item-section>{{ params.subject }}</q-item-section></q-item>
@ -285,7 +301,8 @@ const recipientColumns = [
{ name: 'email', label: 'Email', field: 'email', align: 'left' },
{ name: 'civic_address', label: 'Adresse', field: 'civic_address', align: 'left' },
{ name: 'gift_url', label: 'Lien-cadeau', field: 'gift_url', align: 'left' },
{ name: 'match', label: 'Match client', field: 'match_method', align: 'left' },
{ name: 'language', label: 'Langue', field: 'language', align: 'left' },
{ name: 'match', label: 'Match', field: 'match_method', align: 'left' },
{ name: 'customer_name', label: 'Client ERPNext', field: 'customer_name', align: 'left' },
{ name: 'actions', label: '', field: '', align: 'right' },
]
@ -304,6 +321,19 @@ const unmatchedCount = computed(() => recipients.value.filter(r => !r.customer_i
const excludedCount = computed(() => recipients.value.filter(r => r.excluded).length)
// Net number of emails that will actually be fired off (paired AND not excluded)
const sendableCount = computed(() => recipients.value.filter(r => !r.excluded && r.gift_url).length)
// FR / EN breakdown of the sendable recipients useful preview before launch
// so the user knows which template will actually be used and how many.
const langBreakdown = computed(() => {
const counts = {}
for (const r of recipients.value) {
if (r.excluded || !r.gift_url) continue
const lang = (r.language || 'fr').toLowerCase()
counts[lang] = (counts[lang] || 0) + 1
}
const parts = Object.entries(counts).map(([l, n]) => `${n} × ${l.toUpperCase()}`)
return parts.length ? parts.join(', ') : '—'
})
const estimatedMinutes = computed(() => {
const per = (params.value.throttle_ms || 600) / 1000
return Math.max(1, Math.round((sendableCount.value * per) / 60))

View File

@ -50,7 +50,13 @@
Blocs conditionnels : <code>{{#expiry}}...{{/expiry}}</code>.
</span>
</q-banner>
<!-- lang+spellcheck attributes turn on the browser's native spell-checker.
Red squiggles under typos in real time. Language follows the current
template suffix (-fr fr, -en en) so Chrome/Safari use the right
dictionary. For AI-grade rewriting (grammar, tone) plug the future
/campaigns/ai/rewrite endpoint into a separate button. -->
<q-input v-model="html" type="textarea" outlined dense filled
:input-attrs="{ spellcheck: 'true', lang: editorLang, autocorrect: 'on', autocapitalize: 'sentences' }"
input-style="font-family:monospace; font-size:0.85rem; line-height:1.4; min-height:calc(100vh - 220px)"
@update:model-value="dirty = true" />
</div>
@ -62,7 +68,7 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useQuasar } from 'quasar'
import { listTemplates, getTemplate, saveTemplate, previewTemplate } from 'src/api/campaigns'
@ -84,6 +90,14 @@ const saving = ref(false)
const viewMode = ref('visual') // 'visual' | 'html' | 'preview'
const blankCanvas = ref(false) // true after clicking "Vide" hides the "use HTML" hint
// Derive the editor's spell-check language from the template name suffix.
// gift-email-fr fr, gift-email-en en, etc. Browser's built-in dictionary
// (Chrome/Safari/Firefox) uses this to flag typos in the correct language.
const editorLang = computed(() => {
const m = (currentName.value || '').match(/-([a-z]{2})$/)
return m ? m[1] : 'fr'
})
let editor = null
let originalHtml = ''

View File

@ -0,0 +1,355 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>An exclusive offer from TARGO</title>
<!-- Brand fonts: Space Grotesk for display, Plus Jakarta Sans for body.
Wrapped in MSO conditional comment so Outlook desktop skips the
Google Fonts request (it can't render them anyway) and falls back
to Helvetica via the font-family stack on each element. -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<!--<![endif]-->
</head>
<body style="margin:0; padding:0; background:#F5FAF7; font-family:'Plus Jakarta Sans','Helvetica Neue',Helvetica,Arial,sans-serif; color:#1B2E24; line-height:1.5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style="padding:32px 16px;">
<!-- Main card -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:12px; overflow:hidden;">
<!-- Logo header (clean, no colored band) -->
<tr>
<td style="padding:28px 36px 22px; border-bottom:1px solid #eef0ee;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/eed4d18c-8065-4c5f-b47c-58af63171cd0/content"
alt="TARGO" width="140"
style="display:block; border:0; outline:none; text-decoration:none; max-width:140px; height:auto;">
</td>
</tr>
<!-- Greeting + hook -->
<tr>
<td style="padding:26px 36px 4px;">
<p style="margin:0 0 14px; font-size:1rem; color:#374151;">Hi {{firstname}},</p>
<p style="margin:0 0 10px; font-size:1.08rem; color:#1B2E24; font-weight:500;">
You went local — we want to say thanks.
</p>
<p style="margin:0; font-size:1rem; color:#374151;">
Summer's here, and so is your
<strong>limited-time exclusive offer</strong>:
</p>
</td>
</tr>
<!-- Info pill: gift card amount -->
<tr>
<td style="padding:18px 36px 8px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 18px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Digital gift card
</div>
<div style="font-size:1.05rem; font-weight:700; color:#1B2E24;">
🎁 {{amount}} at hundreds of brands
</div>
</div>
</td>
</tr>
<!-- Two-column: DELIVERY + CONDITION -->
<tr>
<td style="padding:6px 36px 18px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td width="50%" style="padding-right:5px; vertical-align:top;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Delivery
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
⚡ Instant on activation
</div>
</div>
</td>
<td width="50%" style="padding-left:5px; vertical-align:top;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Condition
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
🤝 Stay with us {{commitment_months}}+ months
</div>
</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- Divider -->
<tr><td style="padding:0 36px;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<!-- Option 1 chip -->
<tr>
<td style="padding:22px 36px 10px;">
<span style="display:inline-block; background:#E6F9EE; color:#00C853; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
✅ Option 1
</span>
</td>
</tr>
<!-- Big green CTA card -->
<tr>
<td style="padding:0 36px 8px;">
<a href="{{gift_url}}" style="text-decoration:none; color:#ffffff; display:block;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
bgcolor="#00C853"
style="background:#00C853; background-image:linear-gradient(135deg,#00C853 0%,#005026 100%); border-radius:12px;">
<tr>
<td style="padding:30px 24px; text-align:center;">
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:2.2rem; font-weight:700; line-height:1; margin-bottom:14px; color:#ffffff;">
🎁&nbsp;&nbsp;{{amount}}
</div>
<div style="font-size:1.08rem; font-weight:700; color:#ffffff;">
Redeem my gift card
</div>
<div style="font-size:0.85rem; opacity:0.9; margin-top:8px; color:#ffffff;">
Pick your brand on Giftbit →
</div>
</td>
</tr>
</table>
</a>
</td>
</tr>
<!-- Prorata refund disclaimer -->
<tr>
<td style="padding:10px 36px 22px;">
<div style="font-size:0.85rem; color:#6b7280;">
🪂 If you leave before {{commitment_months}} months, the prorated portion is refundable.
</div>
</td>
</tr>
<!-- Divider -->
<tr><td style="padding:0 36px;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<!-- Option 2 chip -->
<tr>
<td style="padding:22px 36px 8px;">
<span style="display:inline-block; background:#F5FAF7; color:#6b7280; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
⏭️ Option 2
</span>
</td>
</tr>
<tr>
<td style="padding:0 36px 22px;">
<div style="font-size:0.97rem; color:#4b5563; line-height:1.55;">
Do nothing. Your monthly subscription continues as usual —
no commitment, no gift card.
</div>
</td>
</tr>
{{#expiry}}
<!-- Optional expiry callout -->
<tr><td style="padding:0 36px;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<tr>
<td style="padding:18px 36px 0;">
<div style="font-size:0.85rem; color:#9ca3af;">
⏰ This offer expires on <strong style="color:#374151;">{{expiry}}</strong>.
</div>
</td>
</tr>
{{/expiry}}
<!-- Divider -->
<tr><td style="padding:18px 36px 0;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<!-- Signature -->
<tr>
<td style="padding:22px 36px 28px;">
<div style="font-size:0.97rem; color:#1B2E24;">
🤝 Thanks for helping our regional economy thrive!
</div>
<div style="font-size:0.9rem; color:#6b7280; margin-top:6px;">
The TARGO team
</div>
</td>
</tr>
</table>
<!-- Merchant brands grid — 4 cols × 3 rows = 12 logos
TO SWAP TO MAILJET-HOSTED LOGOS:
Replace each placeholder src URL below with the Mailjet CDN URL
you already have (same format as the TARGO logo:
https://xqy3m.mjt.lu/img2/xqy3m/<UUID>/content). The alt= attribute
stays as-is (used by screen readers + shown when images blocked).
Brand list in order: Amazon, IGA, Tim Hortons, $1 Plus (Dollarama),
Pizza Pizza, Home Depot, Best Buy, Walmart,
Petro-Canada, Esso, Home Hardware, Sobeys.
-->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; margin-top:8px;">
<tr>
<td style="padding:24px 36px 12px; text-align:center;">
<div style="font-size:1.02rem; font-weight:700; color:#00C853;">
Quelques exemples de choix pour votre carte cadeau :
</div>
</td>
</tr>
<tr>
<td style="padding:0 28px 8px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<!-- Row 1 — real Mailjet-hosted logos -->
<tr>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/31ffdf91-d2de-4ced-8b99-ad2221695abe/content" alt="Amazon" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/9c9dfa18-2a3a-414a-b5ad-16d490c961b5/content" alt="IGA" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/4b0b2a4a-5f99-416c-8873-8d3e4389b6f7/content" alt="Tim Hortons" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/162b988c-beb7-49b3-b85e-ccc12fa2c155/content" alt="$1 Plus" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
</tr>
<!-- Row 2 -->
<tr>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/e7771a?text=Pizza+Pizza" alt="Pizza Pizza" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/f96302?text=Home+Depot" alt="Home Depot" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/000000?text=Best+Buy" alt="Best Buy" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/0071ce?text=Walmart" alt="Walmart" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
</tr>
<!-- Row 3 -->
<tr>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/e1140a?text=Petro-Canada" alt="Petro-Canada" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/004b8d?text=Esso" alt="Esso" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/e6332a?text=Home+Hardware" alt="Home Hardware" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/4caf50?text=Sobeys" alt="Sobeys" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- "Why this email" + official contacts (per brand guide §11) -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px;">
<tr>
<td style="padding:18px 36px 8px; text-align:center;">
<div style="font-size:0.78rem; color:#64748B; line-height:1.55;">
You're getting this email because you're a TARGO customer at
<strong style="color:#1B2E24;">{{description}}</strong>.<br>
Got a question? Write to
<a href="mailto:support@targo.ca" style="color:#00C853; text-decoration:none;">support@targo.ca</a>
or call
<a href="tel:5144480773" style="color:#00C853; text-decoration:none;">514&nbsp;448-0773</a>
/ <a href="tel:18558882746" style="color:#00C853; text-decoration:none;">1&nbsp;855&nbsp;888-2746</a>.
Support&nbsp;7&nbsp;days/week.
</div>
</td>
</tr>
</table>
<!-- Dark footer band — official slogan + inverted wordmark + address.
Slogan stays in French per brand identity (it's the trademark
tagline of the company, not a translatable marketing line). -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; margin-top:12px; background:#1C1E26; border-radius:12px; overflow:hidden;">
<tr>
<td style="padding:26px 36px 22px; text-align:center;">
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:1.4rem; font-weight:700; letter-spacing:0.18em; color:#ffffff; line-height:1;">
TARGO<span style="color:#00C853;">.</span>
</div>
<div style="font-size:0.85rem; color:rgba(255,255,255,0.75); margin-top:10px;">
Services de confiance, tout-en-un, près de chez vous<span style="color:#00C853; font-weight:700;">.</span>
</div>
<div style="font-size:0.7rem; color:rgba(255,255,255,0.45); margin-top:14px; line-height:1.5;">
<a href="https://www.targo.ca" style="color:rgba(255,255,255,0.7); text-decoration:none;">www.targo.ca</a>
&nbsp;·&nbsp; 1867 ch. de la rivière, Ste-Clotilde, QC<br>
© {{year}} TARGO Communications · All rights reserved.
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -4,8 +4,17 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Une offre exclusive de TARGO</title>
<!-- Brand fonts: Space Grotesk for display, Plus Jakarta Sans for body.
Wrapped in MSO conditional comment so Outlook desktop skips the
Google Fonts request (it can't render them anyway) and falls back
to Helvetica via the font-family stack on each element. -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<!--<![endif]-->
</head>
<body style="margin:0; padding:0; background:#f7f8f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color:#1f2937; line-height:1.5;">
<body style="margin:0; padding:0; background:#F5FAF7; font-family:'Plus Jakarta Sans','Helvetica Neue',Helvetica,Arial,sans-serif; color:#1B2E24; line-height:1.5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
@ -13,7 +22,7 @@
<!-- Main card -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:14px; overflow:hidden;">
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:12px; overflow:hidden;">
<!-- Logo header (clean, no colored band) -->
<tr>
@ -28,7 +37,7 @@
<tr>
<td style="padding:26px 36px 4px;">
<p style="margin:0 0 14px; font-size:1rem; color:#374151;">Bonjour {{firstname}},</p>
<p style="margin:0 0 10px; font-size:1.08rem; color:#1f2937; font-weight:500;">
<p style="margin:0 0 10px; font-size:1.08rem; color:#1B2E24; font-weight:500;">
Tu choisis local, on veut te remercier.
</p>
<p style="margin:0; font-size:1rem; color:#374151;">
@ -41,11 +50,11 @@
<!-- Info pill: gift card amount -->
<tr>
<td style="padding:18px 36px 8px;">
<div style="background:#f3f4f3; border-radius:10px; padding:14px 18px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 18px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Carte-cadeau numérique
</div>
<div style="font-size:1.05rem; font-weight:700; color:#1f2937;">
<div style="font-size:1.05rem; font-weight:700; color:#1B2E24;">
🎁 {{amount}} chez des centaines de marques
</div>
</div>
@ -58,21 +67,21 @@
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td width="50%" style="padding-right:5px; vertical-align:top;">
<div style="background:#f3f4f3; border-radius:10px; padding:14px 16px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Envoi
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1f2937;">
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
⚡ Instantané à l'activation
</div>
</div>
</td>
<td width="50%" style="padding-left:5px; vertical-align:top;">
<div style="background:#f3f4f3; border-radius:10px; padding:14px 16px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Condition
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1f2937;">
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
🤝 Rester encore {{commitment_months}} mois ou +
</div>
</div>
@ -88,7 +97,7 @@
<!-- Option 1 chip -->
<tr>
<td style="padding:22px 36px 10px;">
<span style="display:inline-block; background:#dcf4e3; color:#019547; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
<span style="display:inline-block; background:#E6F9EE; color:#00C853; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
✅ Option 1
</span>
</td>
@ -98,11 +107,16 @@
<tr>
<td style="padding:0 36px 8px;">
<a href="{{gift_url}}" style="text-decoration:none; color:#ffffff; display:block;">
<!-- CTA card — gradient Targo officiel (135deg, #00C853 → #005026).
Outlook desktop will ignore the gradient and render the
solid #00C853 fallback (the gradient is the bgcolor's
fallback in nodemailer rendering). Acceptable degradation. -->
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
style="background:#019547; border-radius:14px;">
bgcolor="#00C853"
style="background:#00C853; background-image:linear-gradient(135deg,#00C853 0%,#005026 100%); border-radius:12px;">
<tr>
<td style="padding:30px 24px; text-align:center;">
<div style="font-size:2.1rem; font-weight:800; line-height:1; margin-bottom:14px; color:#ffffff;">
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:2.2rem; font-weight:700; line-height:1; margin-bottom:14px; color:#ffffff;">
🎁&nbsp;&nbsp;{{amount}}
</div>
<div style="font-size:1.08rem; font-weight:700; color:#ffffff;">
@ -133,7 +147,7 @@
<!-- Option 2 chip -->
<tr>
<td style="padding:22px 36px 8px;">
<span style="display:inline-block; background:#f3f4f3; color:#6b7280; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
<span style="display:inline-block; background:#F5FAF7; color:#6b7280; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
⏭️ Option 2
</span>
</td>
@ -165,7 +179,7 @@
<!-- Signature -->
<tr>
<td style="padding:22px 36px 28px;">
<div style="font-size:0.97rem; color:#1f2937;">
<div style="font-size:0.97rem; color:#1B2E24;">
🤝 Merci de faire rouler l'économie de notre région avec nous !
</div>
<div style="font-size:0.9rem; color:#6b7280; margin-top:6px;">
@ -190,7 +204,7 @@
style="max-width:600px; margin-top:8px;">
<tr>
<td style="padding:24px 36px 12px; text-align:center;">
<div style="font-size:1.02rem; font-weight:700; color:#019547;">
<div style="font-size:1.02rem; font-weight:700; color:#00C853;">
Quelques exemples de choix pour votre carte cadeau :
</div>
</td>
@ -296,28 +310,42 @@
</tr>
</table>
<!-- Footer (outside the card, small print) -->
<!-- "Pourquoi cet email" + coordonnées officielles (per brand guide §11) -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px;">
<tr>
<td style="padding:18px 36px 8px; text-align:center;">
<div style="font-size:0.75rem; color:#9ca3af; line-height:1.55;">
<div style="font-size:0.78rem; color:#64748B; line-height:1.55;">
Tu reçois ce courriel parce que tu es client(e) TARGO à
<strong style="color:#6b7280;">{{description}}</strong>.<br>
<strong style="color:#1B2E24;">{{description}}</strong>.<br>
Une question ? Écris-nous à
<a href="mailto:facturation@targointernet.com" style="color:#019547;">facturation@targointernet.com</a>
ou appelle au <a href="tel:5142421500" style="color:#019547;">514 242-1500</a>.
<a href="mailto:support@targo.ca" style="color:#00C853; text-decoration:none;">support@targo.ca</a>
ou appelle au
<a href="tel:5144480773" style="color:#00C853; text-decoration:none;">514&nbsp;448-0773</a>
/ <a href="tel:18558882746" style="color:#00C853; text-decoration:none;">1&nbsp;855&nbsp;888-2746</a>.
Support&nbsp;7j/7.
</div>
</td>
</tr>
</table>
<!-- Dark footer band — slogan officiel + logo inversé + adresse -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; margin-top:12px; background:#1C1E26; border-radius:12px; overflow:hidden;">
<tr>
<td style="padding:0 36px 28px; text-align:center;">
<div style="font-size:0.7rem; color:#9ca3af;">
<strong style="color:#019547; letter-spacing:0.08em;">TARGO</strong>
— Internet fibre optique au Québec — service <em>Gigafibre</em><br>
<a href="https://www.targointernet.com" style="color:#9ca3af; text-decoration:underline;">targointernet.com</a>
&middot;
<a href="https://www.gigafibre.ca" style="color:#9ca3af; text-decoration:underline;">gigafibre.ca</a>
<td style="padding:26px 36px 22px; text-align:center;">
<!-- TARGO wordmark in white (matches brand "fonds sombres" usage) -->
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:1.4rem; font-weight:700; letter-spacing:0.18em; color:#ffffff; line-height:1;">
TARGO<span style="color:#00C853;">.</span>
</div>
<!-- Slogan officiel — toujours avec le point vert -->
<div style="font-size:0.85rem; color:rgba(255,255,255,0.75); margin-top:10px;">
Services de confiance, tout-en-un, près de chez vous<span style="color:#00C853; font-weight:700;">.</span>
</div>
<div style="font-size:0.7rem; color:rgba(255,255,255,0.45); margin-top:14px; line-height:1.5;">
<a href="https://www.targo.ca" style="color:rgba(255,255,255,0.7); text-decoration:none;">www.targo.ca</a>
&nbsp;·&nbsp; 1867 ch. de la rivière, Ste-Clotilde, QC<br>
© {{year}} TARGO Communications · Tous droits réservés.
</div>
</td>
</tr>

View File

@ -266,12 +266,13 @@ async function matchCustomer (recipient) {
if (recipient.email) {
try {
const r = await erp.list('Customer', {
fields: ['name', 'customer_name', 'email_id', 'mobile_no'],
fields: ['name', 'customer_name', 'email_id', 'mobile_no', 'language'],
filters: [['email_id', '=', recipient.email]],
limit: 1,
})
if (r && r.length) {
return { customer_id: r[0].name, customer_name: r[0].customer_name,
language: r[0].language || 'fr',
match_method: 'email', match_confidence: 1.0 }
}
} catch (e) { log('match email error:', e.message) }
@ -280,22 +281,21 @@ async function matchCustomer (recipient) {
// 2) Phone match
if (recipient.phone) {
try {
// Try both mobile_no (with various formattings) and the canonical 10-digit form
const r = await erp.list('Customer', {
fields: ['name', 'customer_name', 'mobile_no'],
fields: ['name', 'customer_name', 'mobile_no', 'language'],
filters: [['mobile_no', 'like', '%' + recipient.phone.slice(-7) + '%']],
limit: 5,
})
// Filter to exact digit match
const hit = (r || []).find(c => normalizePhone(c.mobile_no) === recipient.phone)
if (hit) {
return { customer_id: hit.name, customer_name: hit.customer_name,
language: hit.language || 'fr',
match_method: 'phone', match_confidence: 0.9 }
}
} catch (e) { log('match phone error:', e.message) }
}
// 3) Civic + postal on Service Location → Customer
// 3) Civic + postal on Service Location → Customer (+ fetch Customer.language separately)
if (recipient.civic_address && recipient.postal_code) {
try {
const civic = normalizeCivic(recipient.civic_address)
@ -315,14 +315,25 @@ async function matchCustomer (recipient) {
slCivic.split('|')[1]?.startsWith(streetPrefix.slice(0, 5))
})
if (hit && hit.customer) {
// Service Location doesn't carry language — fetch it from the linked Customer
let language = 'fr'
try {
const c = await erp.list('Customer', {
fields: ['language'], filters: [['name', '=', hit.customer]], limit: 1,
})
if (c?.[0]?.language) language = c[0].language
} catch {}
return { customer_id: hit.customer, customer_name: hit.customer_name || hit.customer,
language,
match_method: 'civic', match_confidence: 0.8 }
}
}
} catch (e) { log('match civic error:', e.message) }
}
return { customer_id: null, customer_name: null, match_method: null, match_confidence: 0 }
// Unmatched: default to French (93% of customer base)
return { customer_id: null, customer_name: null, language: 'fr',
match_method: null, match_confidence: 0 }
}
// ── Storage helpers ──────────────────────────────────────────────────────────
@ -382,17 +393,26 @@ function renderTemplate (tpl, vars) {
})
}
// Default template path — bundled inside the hub at services/targo-hub/templates/.
// Can be overridden per campaign via params.template_path (e.g. for A/B testing).
// Keep the file in sync with scripts/campaigns/templates/gift-email-fr.html
// (the CLI uses that copy for one-off batch sends outside the hub).
// Templates are bundled inside the hub at services/targo-hub/templates/.
// Keep them in sync with scripts/campaigns/templates/ for the CLI use case.
const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates')
const DEFAULT_TEMPLATE = path.join(TEMPLATES_DIR, 'gift-email-fr.html')
// Allow-list templates that can be edited via the API. Anything outside this
// list is rejected (path-traversal defence + intent: only campaign-related
// templates are exposed; contract-residential.html etc. are NOT editable here).
const EDITABLE_TEMPLATES = ['gift-email-fr']
// Allow-list templates that can be edited via the API. Naming convention:
// gift-email-<lang> — the recipient's language drives which one gets used
// at send time. Adding a new lang = drop a new .html here + add to this list.
const EDITABLE_TEMPLATES = ['gift-email-fr', 'gift-email-en']
// Resolve the template path for a given recipient language. Falls back to
// the French version (most of the customer base) if the language-specific
// file doesn't exist. Per-campaign override via params.template_path skips
// language routing entirely.
function templateForLanguage (lang) {
const safe = (lang || 'fr').toLowerCase().split('-')[0] // 'fr-CA' → 'fr'
const candidate = path.join(TEMPLATES_DIR, `gift-email-${safe}.html`)
if (fs.existsSync(candidate)) return candidate
return DEFAULT_TEMPLATE
}
function templatePath (name) {
if (!EDITABLE_TEMPLATES.includes(name)) {
@ -447,8 +467,17 @@ async function sendCampaignAsync (id) {
sse.broadcast(topic, 'campaign-status', { id, status: 'sending' })
const p = campaign.params || {}
const tplText = readTemplate(p.template_path)
const throttle = parseInt(p.throttle_ms || 600, 10)
// Pre-load templates by language to avoid re-reading from disk on every
// recipient. Cache map keyed by resolved template path.
const tplCache = new Map()
const getTpl = (lang) => {
const tplPath = p.template_path || templateForLanguage(lang)
if (!tplCache.has(tplPath)) {
tplCache.set(tplPath, fs.readFileSync(tplPath, 'utf8'))
}
return tplCache.get(tplPath)
}
for (let i = 0; i < campaign.recipients.length; i++) {
const r = campaign.recipients[i]
@ -459,8 +488,10 @@ async function sendCampaignAsync (id) {
saveCampaign(campaign)
sse.broadcast(topic, 'recipient-update', { i, recipient: r })
const lang = (r.language || 'fr').toLowerCase().split('-')[0]
const tplText = getTpl(lang)
const vars = {
firstname: r.firstname || 'cher client',
firstname: r.firstname || (lang === 'en' ? 'dear customer' : 'cher client'),
lastname: r.lastname || '',
email: r.email,
description: r.civic_address || '',
@ -468,6 +499,7 @@ async function sendCampaignAsync (id) {
amount: p.amount || '50 $',
expiry: p.expiry || '',
commitment_months: p.commitment_months || '3',
year: new Date().getFullYear(),
}
const html = renderTemplate(tplText, vars)
const toName = `${r.firstname || ''} ${r.lastname || ''}`.trim()

View File

@ -0,0 +1,355 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>An exclusive offer from TARGO</title>
<!-- Brand fonts: Space Grotesk for display, Plus Jakarta Sans for body.
Wrapped in MSO conditional comment so Outlook desktop skips the
Google Fonts request (it can't render them anyway) and falls back
to Helvetica via the font-family stack on each element. -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<!--<![endif]-->
</head>
<body style="margin:0; padding:0; background:#F5FAF7; font-family:'Plus Jakarta Sans','Helvetica Neue',Helvetica,Arial,sans-serif; color:#1B2E24; line-height:1.5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style="padding:32px 16px;">
<!-- Main card -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:12px; overflow:hidden;">
<!-- Logo header (clean, no colored band) -->
<tr>
<td style="padding:28px 36px 22px; border-bottom:1px solid #eef0ee;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/eed4d18c-8065-4c5f-b47c-58af63171cd0/content"
alt="TARGO" width="140"
style="display:block; border:0; outline:none; text-decoration:none; max-width:140px; height:auto;">
</td>
</tr>
<!-- Greeting + hook -->
<tr>
<td style="padding:26px 36px 4px;">
<p style="margin:0 0 14px; font-size:1rem; color:#374151;">Hi {{firstname}},</p>
<p style="margin:0 0 10px; font-size:1.08rem; color:#1B2E24; font-weight:500;">
You went local — we want to say thanks.
</p>
<p style="margin:0; font-size:1rem; color:#374151;">
Summer's here, and so is your
<strong>limited-time exclusive offer</strong>:
</p>
</td>
</tr>
<!-- Info pill: gift card amount -->
<tr>
<td style="padding:18px 36px 8px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 18px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Digital gift card
</div>
<div style="font-size:1.05rem; font-weight:700; color:#1B2E24;">
🎁 {{amount}} at hundreds of brands
</div>
</div>
</td>
</tr>
<!-- Two-column: DELIVERY + CONDITION -->
<tr>
<td style="padding:6px 36px 18px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td width="50%" style="padding-right:5px; vertical-align:top;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Delivery
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
⚡ Instant on activation
</div>
</div>
</td>
<td width="50%" style="padding-left:5px; vertical-align:top;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Condition
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
🤝 Stay with us {{commitment_months}}+ months
</div>
</div>
</td>
</tr>
</table>
</td>
</tr>
<!-- Divider -->
<tr><td style="padding:0 36px;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<!-- Option 1 chip -->
<tr>
<td style="padding:22px 36px 10px;">
<span style="display:inline-block; background:#E6F9EE; color:#00C853; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
✅ Option 1
</span>
</td>
</tr>
<!-- Big green CTA card -->
<tr>
<td style="padding:0 36px 8px;">
<a href="{{gift_url}}" style="text-decoration:none; color:#ffffff; display:block;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
bgcolor="#00C853"
style="background:#00C853; background-image:linear-gradient(135deg,#00C853 0%,#005026 100%); border-radius:12px;">
<tr>
<td style="padding:30px 24px; text-align:center;">
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:2.2rem; font-weight:700; line-height:1; margin-bottom:14px; color:#ffffff;">
🎁&nbsp;&nbsp;{{amount}}
</div>
<div style="font-size:1.08rem; font-weight:700; color:#ffffff;">
Redeem my gift card
</div>
<div style="font-size:0.85rem; opacity:0.9; margin-top:8px; color:#ffffff;">
Pick your brand on Giftbit →
</div>
</td>
</tr>
</table>
</a>
</td>
</tr>
<!-- Prorata refund disclaimer -->
<tr>
<td style="padding:10px 36px 22px;">
<div style="font-size:0.85rem; color:#6b7280;">
🪂 If you leave before {{commitment_months}} months, the prorated portion is refundable.
</div>
</td>
</tr>
<!-- Divider -->
<tr><td style="padding:0 36px;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<!-- Option 2 chip -->
<tr>
<td style="padding:22px 36px 8px;">
<span style="display:inline-block; background:#F5FAF7; color:#6b7280; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
⏭️ Option 2
</span>
</td>
</tr>
<tr>
<td style="padding:0 36px 22px;">
<div style="font-size:0.97rem; color:#4b5563; line-height:1.55;">
Do nothing. Your monthly subscription continues as usual —
no commitment, no gift card.
</div>
</td>
</tr>
{{#expiry}}
<!-- Optional expiry callout -->
<tr><td style="padding:0 36px;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<tr>
<td style="padding:18px 36px 0;">
<div style="font-size:0.85rem; color:#9ca3af;">
⏰ This offer expires on <strong style="color:#374151;">{{expiry}}</strong>.
</div>
</td>
</tr>
{{/expiry}}
<!-- Divider -->
<tr><td style="padding:18px 36px 0;"><div style="border-top:1px solid #eef0ee;"></div></td></tr>
<!-- Signature -->
<tr>
<td style="padding:22px 36px 28px;">
<div style="font-size:0.97rem; color:#1B2E24;">
🤝 Thanks for helping our regional economy thrive!
</div>
<div style="font-size:0.9rem; color:#6b7280; margin-top:6px;">
The TARGO team
</div>
</td>
</tr>
</table>
<!-- Merchant brands grid — 4 cols × 3 rows = 12 logos
TO SWAP TO MAILJET-HOSTED LOGOS:
Replace each placeholder src URL below with the Mailjet CDN URL
you already have (same format as the TARGO logo:
https://xqy3m.mjt.lu/img2/xqy3m/<UUID>/content). The alt= attribute
stays as-is (used by screen readers + shown when images blocked).
Brand list in order: Amazon, IGA, Tim Hortons, $1 Plus (Dollarama),
Pizza Pizza, Home Depot, Best Buy, Walmart,
Petro-Canada, Esso, Home Hardware, Sobeys.
-->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; margin-top:8px;">
<tr>
<td style="padding:24px 36px 12px; text-align:center;">
<div style="font-size:1.02rem; font-weight:700; color:#00C853;">
Quelques exemples de choix pour votre carte cadeau :
</div>
</td>
</tr>
<tr>
<td style="padding:0 28px 8px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<!-- Row 1 — real Mailjet-hosted logos -->
<tr>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/31ffdf91-d2de-4ced-8b99-ad2221695abe/content" alt="Amazon" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/9c9dfa18-2a3a-414a-b5ad-16d490c961b5/content" alt="IGA" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/4b0b2a4a-5f99-416c-8873-8d3e4389b6f7/content" alt="Tim Hortons" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://xqy3m.mjt.lu/img2/xqy3m/162b988c-beb7-49b3-b85e-ccc12fa2c155/content" alt="$1 Plus" width="95" style="max-width:95px; height:auto; display:inline-block; border:0;">
</td></tr>
</table>
</td>
</tr>
<!-- Row 2 -->
<tr>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/e7771a?text=Pizza+Pizza" alt="Pizza Pizza" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/f96302?text=Home+Depot" alt="Home Depot" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/000000?text=Best+Buy" alt="Best Buy" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/0071ce?text=Walmart" alt="Walmart" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
</tr>
<!-- Row 3 -->
<tr>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/e1140a?text=Petro-Canada" alt="Petro-Canada" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/004b8d?text=Esso" alt="Esso" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/e6332a?text=Home+Hardware" alt="Home Hardware" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
<td width="25%" style="padding:4px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#ffffff; border-radius:8px;">
<tr><td align="center" valign="middle" height="92" style="height:92px;">
<img src="https://placehold.co/160x90/ffffff/4caf50?text=Sobeys" alt="Sobeys" width="120" style="max-width:120px; max-height:72px; display:inline-block; border:0;">
</td></tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- "Why this email" + official contacts (per brand guide §11) -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px;">
<tr>
<td style="padding:18px 36px 8px; text-align:center;">
<div style="font-size:0.78rem; color:#64748B; line-height:1.55;">
You're getting this email because you're a TARGO customer at
<strong style="color:#1B2E24;">{{description}}</strong>.<br>
Got a question? Write to
<a href="mailto:support@targo.ca" style="color:#00C853; text-decoration:none;">support@targo.ca</a>
or call
<a href="tel:5144480773" style="color:#00C853; text-decoration:none;">514&nbsp;448-0773</a>
/ <a href="tel:18558882746" style="color:#00C853; text-decoration:none;">1&nbsp;855&nbsp;888-2746</a>.
Support&nbsp;7&nbsp;days/week.
</div>
</td>
</tr>
</table>
<!-- Dark footer band — official slogan + inverted wordmark + address.
Slogan stays in French per brand identity (it's the trademark
tagline of the company, not a translatable marketing line). -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; margin-top:12px; background:#1C1E26; border-radius:12px; overflow:hidden;">
<tr>
<td style="padding:26px 36px 22px; text-align:center;">
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:1.4rem; font-weight:700; letter-spacing:0.18em; color:#ffffff; line-height:1;">
TARGO<span style="color:#00C853;">.</span>
</div>
<div style="font-size:0.85rem; color:rgba(255,255,255,0.75); margin-top:10px;">
Services de confiance, tout-en-un, près de chez vous<span style="color:#00C853; font-weight:700;">.</span>
</div>
<div style="font-size:0.7rem; color:rgba(255,255,255,0.45); margin-top:14px; line-height:1.5;">
<a href="https://www.targo.ca" style="color:rgba(255,255,255,0.7); text-decoration:none;">www.targo.ca</a>
&nbsp;·&nbsp; 1867 ch. de la rivière, Ste-Clotilde, QC<br>
© {{year}} TARGO Communications · All rights reserved.
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -4,8 +4,17 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Une offre exclusive de TARGO</title>
<!-- Brand fonts: Space Grotesk for display, Plus Jakarta Sans for body.
Wrapped in MSO conditional comment so Outlook desktop skips the
Google Fonts request (it can't render them anyway) and falls back
to Helvetica via the font-family stack on each element. -->
<!--[if !mso]><!-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<!--<![endif]-->
</head>
<body style="margin:0; padding:0; background:#f7f8f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color:#1f2937; line-height:1.5;">
<body style="margin:0; padding:0; background:#F5FAF7; font-family:'Plus Jakarta Sans','Helvetica Neue',Helvetica,Arial,sans-serif; color:#1B2E24; line-height:1.5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
@ -13,7 +22,7 @@
<!-- Main card -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:14px; overflow:hidden;">
style="max-width:600px; background:#ffffff; border:1px solid #e5e7eb; border-radius:12px; overflow:hidden;">
<!-- Logo header (clean, no colored band) -->
<tr>
@ -28,7 +37,7 @@
<tr>
<td style="padding:26px 36px 4px;">
<p style="margin:0 0 14px; font-size:1rem; color:#374151;">Bonjour {{firstname}},</p>
<p style="margin:0 0 10px; font-size:1.08rem; color:#1f2937; font-weight:500;">
<p style="margin:0 0 10px; font-size:1.08rem; color:#1B2E24; font-weight:500;">
Tu choisis local, on veut te remercier.
</p>
<p style="margin:0; font-size:1rem; color:#374151;">
@ -41,11 +50,11 @@
<!-- Info pill: gift card amount -->
<tr>
<td style="padding:18px 36px 8px;">
<div style="background:#f3f4f3; border-radius:10px; padding:14px 18px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 18px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Carte-cadeau numérique
</div>
<div style="font-size:1.05rem; font-weight:700; color:#1f2937;">
<div style="font-size:1.05rem; font-weight:700; color:#1B2E24;">
🎁 {{amount}} chez des centaines de marques
</div>
</div>
@ -58,21 +67,21 @@
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td width="50%" style="padding-right:5px; vertical-align:top;">
<div style="background:#f3f4f3; border-radius:10px; padding:14px 16px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Envoi
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1f2937;">
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
⚡ Instantané à l'activation
</div>
</div>
</td>
<td width="50%" style="padding-left:5px; vertical-align:top;">
<div style="background:#f3f4f3; border-radius:10px; padding:14px 16px;">
<div style="background:#F5FAF7; border-radius:10px; padding:14px 16px;">
<div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Condition
</div>
<div style="font-size:0.95rem; font-weight:700; color:#1f2937;">
<div style="font-size:0.95rem; font-weight:700; color:#1B2E24;">
🤝 Rester encore {{commitment_months}} mois ou +
</div>
</div>
@ -88,7 +97,7 @@
<!-- Option 1 chip -->
<tr>
<td style="padding:22px 36px 10px;">
<span style="display:inline-block; background:#dcf4e3; color:#019547; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
<span style="display:inline-block; background:#E6F9EE; color:#00C853; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
✅ Option 1
</span>
</td>
@ -98,11 +107,16 @@
<tr>
<td style="padding:0 36px 8px;">
<a href="{{gift_url}}" style="text-decoration:none; color:#ffffff; display:block;">
<!-- CTA card — gradient Targo officiel (135deg, #00C853 → #005026).
Outlook desktop will ignore the gradient and render the
solid #00C853 fallback (the gradient is the bgcolor's
fallback in nodemailer rendering). Acceptable degradation. -->
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
style="background:#019547; border-radius:14px;">
bgcolor="#00C853"
style="background:#00C853; background-image:linear-gradient(135deg,#00C853 0%,#005026 100%); border-radius:12px;">
<tr>
<td style="padding:30px 24px; text-align:center;">
<div style="font-size:2.1rem; font-weight:800; line-height:1; margin-bottom:14px; color:#ffffff;">
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:2.2rem; font-weight:700; line-height:1; margin-bottom:14px; color:#ffffff;">
🎁&nbsp;&nbsp;{{amount}}
</div>
<div style="font-size:1.08rem; font-weight:700; color:#ffffff;">
@ -133,7 +147,7 @@
<!-- Option 2 chip -->
<tr>
<td style="padding:22px 36px 8px;">
<span style="display:inline-block; background:#f3f4f3; color:#6b7280; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
<span style="display:inline-block; background:#F5FAF7; color:#6b7280; font-size:0.82rem; font-weight:700; padding:5px 12px; border-radius:6px;">
⏭️ Option 2
</span>
</td>
@ -165,7 +179,7 @@
<!-- Signature -->
<tr>
<td style="padding:22px 36px 28px;">
<div style="font-size:0.97rem; color:#1f2937;">
<div style="font-size:0.97rem; color:#1B2E24;">
🤝 Merci de faire rouler l'économie de notre région avec nous !
</div>
<div style="font-size:0.9rem; color:#6b7280; margin-top:6px;">
@ -190,7 +204,7 @@
style="max-width:600px; margin-top:8px;">
<tr>
<td style="padding:24px 36px 12px; text-align:center;">
<div style="font-size:1.02rem; font-weight:700; color:#019547;">
<div style="font-size:1.02rem; font-weight:700; color:#00C853;">
Quelques exemples de choix pour votre carte cadeau :
</div>
</td>
@ -296,28 +310,42 @@
</tr>
</table>
<!-- Footer (outside the card, small print) -->
<!-- "Pourquoi cet email" + coordonnées officielles (per brand guide §11) -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px;">
<tr>
<td style="padding:18px 36px 8px; text-align:center;">
<div style="font-size:0.75rem; color:#9ca3af; line-height:1.55;">
<div style="font-size:0.78rem; color:#64748B; line-height:1.55;">
Tu reçois ce courriel parce que tu es client(e) TARGO à
<strong style="color:#6b7280;">{{description}}</strong>.<br>
<strong style="color:#1B2E24;">{{description}}</strong>.<br>
Une question ? Écris-nous à
<a href="mailto:facturation@targointernet.com" style="color:#019547;">facturation@targointernet.com</a>
ou appelle au <a href="tel:5142421500" style="color:#019547;">514 242-1500</a>.
<a href="mailto:support@targo.ca" style="color:#00C853; text-decoration:none;">support@targo.ca</a>
ou appelle au
<a href="tel:5144480773" style="color:#00C853; text-decoration:none;">514&nbsp;448-0773</a>
/ <a href="tel:18558882746" style="color:#00C853; text-decoration:none;">1&nbsp;855&nbsp;888-2746</a>.
Support&nbsp;7j/7.
</div>
</td>
</tr>
</table>
<!-- Dark footer band — slogan officiel + logo inversé + adresse -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px; margin-top:12px; background:#1C1E26; border-radius:12px; overflow:hidden;">
<tr>
<td style="padding:0 36px 28px; text-align:center;">
<div style="font-size:0.7rem; color:#9ca3af;">
<strong style="color:#019547; letter-spacing:0.08em;">TARGO</strong>
— Internet fibre optique au Québec — service <em>Gigafibre</em><br>
<a href="https://www.targointernet.com" style="color:#9ca3af; text-decoration:underline;">targointernet.com</a>
&middot;
<a href="https://www.gigafibre.ca" style="color:#9ca3af; text-decoration:underline;">gigafibre.ca</a>
<td style="padding:26px 36px 22px; text-align:center;">
<!-- TARGO wordmark in white (matches brand "fonds sombres" usage) -->
<div style="font-family:'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif; font-size:1.4rem; font-weight:700; letter-spacing:0.18em; color:#ffffff; line-height:1;">
TARGO<span style="color:#00C853;">.</span>
</div>
<!-- Slogan officiel — toujours avec le point vert -->
<div style="font-size:0.85rem; color:rgba(255,255,255,0.75); margin-top:10px;">
Services de confiance, tout-en-un, près de chez vous<span style="color:#00C853; font-weight:700;">.</span>
</div>
<div style="font-size:0.7rem; color:rgba(255,255,255,0.45); margin-top:14px; line-height:1.5;">
<a href="https://www.targo.ca" style="color:rgba(255,255,255,0.7); text-decoration:none;">www.targo.ca</a>
&nbsp;·&nbsp; 1867 ch. de la rivière, Ste-Clotilde, QC<br>
© {{year}} TARGO Communications · Tous droits réservés.
</div>
</td>
</tr>