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> </a>
</q-td> </q-td>
</template> </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"> <template v-slot:body-cell-match="props">
<q-td :props="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" /> <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-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>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>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>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>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> <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: 'email', label: 'Email', field: 'email', align: 'left' },
{ name: 'civic_address', label: 'Adresse', field: 'civic_address', align: 'left' }, { name: 'civic_address', label: 'Adresse', field: 'civic_address', align: 'left' },
{ name: 'gift_url', label: 'Lien-cadeau', field: 'gift_url', 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: 'customer_name', label: 'Client ERPNext', field: 'customer_name', align: 'left' },
{ name: 'actions', label: '', field: '', align: 'right' }, { 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) const excludedCount = computed(() => recipients.value.filter(r => r.excluded).length)
// Net number of emails that will actually be fired off (paired AND not excluded) // 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) 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 estimatedMinutes = computed(() => {
const per = (params.value.throttle_ms || 600) / 1000 const per = (params.value.throttle_ms || 600) / 1000
return Math.max(1, Math.round((sendableCount.value * per) / 60)) return Math.max(1, Math.round((sendableCount.value * per) / 60))

View File

@ -50,7 +50,13 @@
Blocs conditionnels : <code>{{#expiry}}...{{/expiry}}</code>. Blocs conditionnels : <code>{{#expiry}}...{{/expiry}}</code>.
</span> </span>
</q-banner> </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 <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)" input-style="font-family:monospace; font-size:0.85rem; line-height:1.4; min-height:calc(100vh - 220px)"
@update:model-value="dirty = true" /> @update:model-value="dirty = true" />
</div> </div>
@ -62,7 +68,7 @@
</template> </template>
<script setup> <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 { useRoute, useRouter } from 'vue-router'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import { listTemplates, getTemplate, saveTemplate, previewTemplate } from 'src/api/campaigns' 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 viewMode = ref('visual') // 'visual' | 'html' | 'preview'
const blankCanvas = ref(false) // true after clicking "Vide" hides the "use HTML" hint 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 editor = null
let originalHtml = '' 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 charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Une offre exclusive de TARGO</title> <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> </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"> <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr> <tr>
@ -13,7 +22,7 @@
<!-- Main card --> <!-- Main card -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" <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) --> <!-- Logo header (clean, no colored band) -->
<tr> <tr>
@ -28,7 +37,7 @@
<tr> <tr>
<td style="padding:26px 36px 4px;"> <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 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. Tu choisis local, on veut te remercier.
</p> </p>
<p style="margin:0; font-size:1rem; color:#374151;"> <p style="margin:0; font-size:1rem; color:#374151;">
@ -41,11 +50,11 @@
<!-- Info pill: gift card amount --> <!-- Info pill: gift card amount -->
<tr> <tr>
<td style="padding:18px 36px 8px;"> <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;"> <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 Carte-cadeau numérique
</div> </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 🎁 {{amount}} chez des centaines de marques
</div> </div>
</div> </div>
@ -58,21 +67,21 @@
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"> <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr> <tr>
<td width="50%" style="padding-right:5px; vertical-align:top;"> <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;"> <div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Envoi Envoi
</div> </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 ⚡ Instantané à l'activation
</div> </div>
</div> </div>
</td> </td>
<td width="50%" style="padding-left:5px; vertical-align:top;"> <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;"> <div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Condition Condition
</div> </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 + 🤝 Rester encore {{commitment_months}} mois ou +
</div> </div>
</div> </div>
@ -88,7 +97,7 @@
<!-- Option 1 chip --> <!-- Option 1 chip -->
<tr> <tr>
<td style="padding:22px 36px 10px;"> <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 ✅ Option 1
</span> </span>
</td> </td>
@ -98,11 +107,16 @@
<tr> <tr>
<td style="padding:0 36px 8px;"> <td style="padding:0 36px 8px;">
<a href="{{gift_url}}" style="text-decoration:none; color:#ffffff; display:block;"> <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" <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> <tr>
<td style="padding:30px 24px; text-align:center;"> <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}} 🎁&nbsp;&nbsp;{{amount}}
</div> </div>
<div style="font-size:1.08rem; font-weight:700; color:#ffffff;"> <div style="font-size:1.08rem; font-weight:700; color:#ffffff;">
@ -133,7 +147,7 @@
<!-- Option 2 chip --> <!-- Option 2 chip -->
<tr> <tr>
<td style="padding:22px 36px 8px;"> <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 ⏭️ Option 2
</span> </span>
</td> </td>
@ -165,7 +179,7 @@
<!-- Signature --> <!-- Signature -->
<tr> <tr>
<td style="padding:22px 36px 28px;"> <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 ! 🤝 Merci de faire rouler l'économie de notre région avec nous !
</div> </div>
<div style="font-size:0.9rem; color:#6b7280; margin-top:6px;"> <div style="font-size:0.9rem; color:#6b7280; margin-top:6px;">
@ -190,7 +204,7 @@
style="max-width:600px; margin-top:8px;"> style="max-width:600px; margin-top:8px;">
<tr> <tr>
<td style="padding:24px 36px 12px; text-align:center;"> <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 : Quelques exemples de choix pour votre carte cadeau :
</div> </div>
</td> </td>
@ -296,28 +310,42 @@
</tr> </tr>
</table> </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" <table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px;"> style="max-width:600px;">
<tr> <tr>
<td style="padding:18px 36px 8px; text-align:center;"> <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 à 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 à Une question ? Écris-nous à
<a href="mailto:facturation@targointernet.com" style="color:#019547;">facturation@targointernet.com</a> <a href="mailto:support@targo.ca" style="color:#00C853; text-decoration:none;">support@targo.ca</a>
ou appelle au <a href="tel:5142421500" style="color:#019547;">514 242-1500</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> </div>
</td> </td>
</tr> </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> <tr>
<td style="padding:0 36px 28px; text-align:center;"> <td style="padding:26px 36px 22px; text-align:center;">
<div style="font-size:0.7rem; color:#9ca3af;"> <!-- TARGO wordmark in white (matches brand "fonds sombres" usage) -->
<strong style="color:#019547; letter-spacing:0.08em;">TARGO</strong> <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;">
— Internet fibre optique au Québec — service <em>Gigafibre</em><br> TARGO<span style="color:#00C853;">.</span>
<a href="https://www.targointernet.com" style="color:#9ca3af; text-decoration:underline;">targointernet.com</a> </div>
&middot; <!-- Slogan officiel — toujours avec le point vert -->
<a href="https://www.gigafibre.ca" style="color:#9ca3af; text-decoration:underline;">gigafibre.ca</a> <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> </div>
</td> </td>
</tr> </tr>

View File

@ -266,12 +266,13 @@ async function matchCustomer (recipient) {
if (recipient.email) { if (recipient.email) {
try { try {
const r = await erp.list('Customer', { 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]], filters: [['email_id', '=', recipient.email]],
limit: 1, limit: 1,
}) })
if (r && r.length) { if (r && r.length) {
return { customer_id: r[0].name, customer_name: r[0].customer_name, return { customer_id: r[0].name, customer_name: r[0].customer_name,
language: r[0].language || 'fr',
match_method: 'email', match_confidence: 1.0 } match_method: 'email', match_confidence: 1.0 }
} }
} catch (e) { log('match email error:', e.message) } } catch (e) { log('match email error:', e.message) }
@ -280,22 +281,21 @@ async function matchCustomer (recipient) {
// 2) Phone match // 2) Phone match
if (recipient.phone) { if (recipient.phone) {
try { try {
// Try both mobile_no (with various formattings) and the canonical 10-digit form
const r = await erp.list('Customer', { 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) + '%']], filters: [['mobile_no', 'like', '%' + recipient.phone.slice(-7) + '%']],
limit: 5, limit: 5,
}) })
// Filter to exact digit match
const hit = (r || []).find(c => normalizePhone(c.mobile_no) === recipient.phone) const hit = (r || []).find(c => normalizePhone(c.mobile_no) === recipient.phone)
if (hit) { if (hit) {
return { customer_id: hit.name, customer_name: hit.customer_name, return { customer_id: hit.name, customer_name: hit.customer_name,
language: hit.language || 'fr',
match_method: 'phone', match_confidence: 0.9 } match_method: 'phone', match_confidence: 0.9 }
} }
} catch (e) { log('match phone error:', e.message) } } 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) { if (recipient.civic_address && recipient.postal_code) {
try { try {
const civic = normalizeCivic(recipient.civic_address) const civic = normalizeCivic(recipient.civic_address)
@ -315,14 +315,25 @@ async function matchCustomer (recipient) {
slCivic.split('|')[1]?.startsWith(streetPrefix.slice(0, 5)) slCivic.split('|')[1]?.startsWith(streetPrefix.slice(0, 5))
}) })
if (hit && hit.customer) { 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, return { customer_id: hit.customer, customer_name: hit.customer_name || hit.customer,
language,
match_method: 'civic', match_confidence: 0.8 } match_method: 'civic', match_confidence: 0.8 }
} }
} }
} catch (e) { log('match civic error:', e.message) } } 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 ────────────────────────────────────────────────────────── // ── Storage helpers ──────────────────────────────────────────────────────────
@ -382,17 +393,26 @@ function renderTemplate (tpl, vars) {
}) })
} }
// Default template path — bundled inside the hub at services/targo-hub/templates/. // Templates are 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 them in sync with scripts/campaigns/templates/ for the CLI use case.
// 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).
const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates') const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates')
const DEFAULT_TEMPLATE = path.join(TEMPLATES_DIR, 'gift-email-fr.html') const DEFAULT_TEMPLATE = path.join(TEMPLATES_DIR, 'gift-email-fr.html')
// Allow-list templates that can be edited via the API. Anything outside this // Allow-list templates that can be edited via the API. Naming convention:
// list is rejected (path-traversal defence + intent: only campaign-related // gift-email-<lang> — the recipient's language drives which one gets used
// templates are exposed; contract-residential.html etc. are NOT editable here). // at send time. Adding a new lang = drop a new .html here + add to this list.
const EDITABLE_TEMPLATES = ['gift-email-fr'] 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) { function templatePath (name) {
if (!EDITABLE_TEMPLATES.includes(name)) { if (!EDITABLE_TEMPLATES.includes(name)) {
@ -447,8 +467,17 @@ async function sendCampaignAsync (id) {
sse.broadcast(topic, 'campaign-status', { id, status: 'sending' }) sse.broadcast(topic, 'campaign-status', { id, status: 'sending' })
const p = campaign.params || {} const p = campaign.params || {}
const tplText = readTemplate(p.template_path)
const throttle = parseInt(p.throttle_ms || 600, 10) 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++) { for (let i = 0; i < campaign.recipients.length; i++) {
const r = campaign.recipients[i] const r = campaign.recipients[i]
@ -459,8 +488,10 @@ async function sendCampaignAsync (id) {
saveCampaign(campaign) saveCampaign(campaign)
sse.broadcast(topic, 'recipient-update', { i, recipient: r }) sse.broadcast(topic, 'recipient-update', { i, recipient: r })
const lang = (r.language || 'fr').toLowerCase().split('-')[0]
const tplText = getTpl(lang)
const vars = { const vars = {
firstname: r.firstname || 'cher client', firstname: r.firstname || (lang === 'en' ? 'dear customer' : 'cher client'),
lastname: r.lastname || '', lastname: r.lastname || '',
email: r.email, email: r.email,
description: r.civic_address || '', description: r.civic_address || '',
@ -468,6 +499,7 @@ async function sendCampaignAsync (id) {
amount: p.amount || '50 $', amount: p.amount || '50 $',
expiry: p.expiry || '', expiry: p.expiry || '',
commitment_months: p.commitment_months || '3', commitment_months: p.commitment_months || '3',
year: new Date().getFullYear(),
} }
const html = renderTemplate(tplText, vars) const html = renderTemplate(tplText, vars)
const toName = `${r.firstname || ''} ${r.lastname || ''}`.trim() 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 charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Une offre exclusive de TARGO</title> <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> </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"> <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr> <tr>
@ -13,7 +22,7 @@
<!-- Main card --> <!-- Main card -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" <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) --> <!-- Logo header (clean, no colored band) -->
<tr> <tr>
@ -28,7 +37,7 @@
<tr> <tr>
<td style="padding:26px 36px 4px;"> <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 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. Tu choisis local, on veut te remercier.
</p> </p>
<p style="margin:0; font-size:1rem; color:#374151;"> <p style="margin:0; font-size:1rem; color:#374151;">
@ -41,11 +50,11 @@
<!-- Info pill: gift card amount --> <!-- Info pill: gift card amount -->
<tr> <tr>
<td style="padding:18px 36px 8px;"> <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;"> <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 Carte-cadeau numérique
</div> </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 🎁 {{amount}} chez des centaines de marques
</div> </div>
</div> </div>
@ -58,21 +67,21 @@
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"> <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr> <tr>
<td width="50%" style="padding-right:5px; vertical-align:top;"> <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;"> <div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Envoi Envoi
</div> </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 ⚡ Instantané à l'activation
</div> </div>
</div> </div>
</td> </td>
<td width="50%" style="padding-left:5px; vertical-align:top;"> <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;"> <div style="font-size:0.7rem; font-weight:700; letter-spacing:0.12em; color:#9ca3af; text-transform:uppercase; margin-bottom:4px;">
Condition Condition
</div> </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 + 🤝 Rester encore {{commitment_months}} mois ou +
</div> </div>
</div> </div>
@ -88,7 +97,7 @@
<!-- Option 1 chip --> <!-- Option 1 chip -->
<tr> <tr>
<td style="padding:22px 36px 10px;"> <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 ✅ Option 1
</span> </span>
</td> </td>
@ -98,11 +107,16 @@
<tr> <tr>
<td style="padding:0 36px 8px;"> <td style="padding:0 36px 8px;">
<a href="{{gift_url}}" style="text-decoration:none; color:#ffffff; display:block;"> <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" <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> <tr>
<td style="padding:30px 24px; text-align:center;"> <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}} 🎁&nbsp;&nbsp;{{amount}}
</div> </div>
<div style="font-size:1.08rem; font-weight:700; color:#ffffff;"> <div style="font-size:1.08rem; font-weight:700; color:#ffffff;">
@ -133,7 +147,7 @@
<!-- Option 2 chip --> <!-- Option 2 chip -->
<tr> <tr>
<td style="padding:22px 36px 8px;"> <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 ⏭️ Option 2
</span> </span>
</td> </td>
@ -165,7 +179,7 @@
<!-- Signature --> <!-- Signature -->
<tr> <tr>
<td style="padding:22px 36px 28px;"> <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 ! 🤝 Merci de faire rouler l'économie de notre région avec nous !
</div> </div>
<div style="font-size:0.9rem; color:#6b7280; margin-top:6px;"> <div style="font-size:0.9rem; color:#6b7280; margin-top:6px;">
@ -190,7 +204,7 @@
style="max-width:600px; margin-top:8px;"> style="max-width:600px; margin-top:8px;">
<tr> <tr>
<td style="padding:24px 36px 12px; text-align:center;"> <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 : Quelques exemples de choix pour votre carte cadeau :
</div> </div>
</td> </td>
@ -296,28 +310,42 @@
</tr> </tr>
</table> </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" <table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
style="max-width:600px;"> style="max-width:600px;">
<tr> <tr>
<td style="padding:18px 36px 8px; text-align:center;"> <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 à 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 à Une question ? Écris-nous à
<a href="mailto:facturation@targointernet.com" style="color:#019547;">facturation@targointernet.com</a> <a href="mailto:support@targo.ca" style="color:#00C853; text-decoration:none;">support@targo.ca</a>
ou appelle au <a href="tel:5142421500" style="color:#019547;">514 242-1500</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> </div>
</td> </td>
</tr> </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> <tr>
<td style="padding:0 36px 28px; text-align:center;"> <td style="padding:26px 36px 22px; text-align:center;">
<div style="font-size:0.7rem; color:#9ca3af;"> <!-- TARGO wordmark in white (matches brand "fonds sombres" usage) -->
<strong style="color:#019547; letter-spacing:0.08em;">TARGO</strong> <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;">
— Internet fibre optique au Québec — service <em>Gigafibre</em><br> TARGO<span style="color:#00C853;">.</span>
<a href="https://www.targointernet.com" style="color:#9ca3af; text-decoration:underline;">targointernet.com</a> </div>
&middot; <!-- Slogan officiel — toujours avec le point vert -->
<a href="https://www.gigafibre.ca" style="color:#9ca3af; text-decoration:underline;">gigafibre.ca</a> <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> </div>
</td> </td>
</tr> </tr>