Copy overhaul + qr designs
This commit is contained in:
246
src/lib/email.ts
246
src/lib/email.ts
@@ -154,7 +154,7 @@ export async function sendPasswordResetEmail(email: string, resetToken: string)
|
||||
Secure QR Code Analytics & Management
|
||||
</p>
|
||||
<p style="margin: 0; color: #999999; font-size: 12px;">
|
||||
© 2026 QR Master. All rights reserved.
|
||||
© 2026 QR Master. All rights reserved.
|
||||
</p>
|
||||
<p style="margin: 15px 0 0 0; color: #aaaaaa; font-size: 11px;">
|
||||
This is an automated security email. Please do not reply.
|
||||
@@ -287,7 +287,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
|
||||
⏰ When Will This Happen?
|
||||
</p>
|
||||
<p style="margin: 0; color: #856404; font-size: 14px; line-height: 1.6;">
|
||||
We're in active development and testing. You'll get an email the moment these features go live – <strong>no waiting, no wondering</strong>. We respect your inbox.
|
||||
We're in active development and testing. You'll get an email the moment these features go live - <strong>no waiting, no wondering</strong>. We respect your inbox.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -297,7 +297,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
|
||||
💡 Want to Get Started Now?
|
||||
</h3>
|
||||
<p style="margin: 0 0 25px 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||||
Our current platform already helps teams create, track, and manage dynamic QR codes. No AI needed – just powerful tools that work today.
|
||||
Our current platform already helps teams create, track, and manage dynamic QR codes. No AI needed - just powerful tools that work today.
|
||||
</p>
|
||||
<a href="https://www.qrmaster.net/signup" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; text-decoration: none; padding: 16px 40px; border-radius: 8px; font-size: 16px; font-weight: 700; display: inline-block; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);">
|
||||
🚀 Try QR Master Free →
|
||||
@@ -310,7 +310,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
|
||||
</p>
|
||||
|
||||
<p style="margin: 20px 0 0 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||||
<strong>— Timo 👋</strong><br>
|
||||
<strong>- Timo 👋</strong><br>
|
||||
<span style="color: #888888; font-size: 14px;">Founder, QR Master</span>
|
||||
</p>
|
||||
</td>
|
||||
@@ -326,7 +326,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
|
||||
<a href="https://www.qrmaster.net" style="color: #667eea; text-decoration: none;">www.qrmaster.net</a>
|
||||
</p>
|
||||
<p style="margin: 0; color: #999999; font-size: 12px;">
|
||||
© 2026 QR Master. All rights reserved.
|
||||
© 2026 QR Master. All rights reserved.
|
||||
</p>
|
||||
<p style="margin: 15px 0 0 0; color: #aaaaaa; font-size: 11px;">
|
||||
You're receiving this because you signed up for AI feature notifications.
|
||||
@@ -461,7 +461,7 @@ export async function sendAIFeatureLaunchEmail(email: string) {
|
||||
<ol style="margin: 0; padding-left: 20px; color: #856404; font-size: 15px; line-height: 1.8;">
|
||||
<li style="margin-bottom: 10px;">🔐 Log in to your QR Master account</li>
|
||||
<li style="margin-bottom: 10px;">👀 Look for the "✨ AI" badge on supported features</li>
|
||||
<li style="margin-bottom: 10px;">✏️ Try creating a QR code – you'll see AI suggestions automatically</li>
|
||||
<li style="margin-bottom: 10px;">✏️ Try creating a QR code - you'll see AI suggestions automatically</li>
|
||||
<li>📈 Check your Analytics tab for the new AI query interface</li>
|
||||
</ol>
|
||||
</div>
|
||||
@@ -483,10 +483,10 @@ export async function sendAIFeatureLaunchEmail(email: string) {
|
||||
<!-- Personal Note -->
|
||||
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 8px; margin: 40px 0;">
|
||||
<p style="margin: 0 0 20px 0; color: #555555; font-size: 15px; line-height: 1.7;">
|
||||
💬 We're excited to see what you build with these new tools. If you have questions, ideas, or just want to share what you created – <a href="mailto:support@qrmaster.net" style="color: #667eea; text-decoration: none;">hit reply</a>. I read every email.
|
||||
💬 We're excited to see what you build with these new tools. If you have questions, ideas, or just want to share what you created - <a href="mailto:support@qrmaster.net" style="color: #667eea; text-decoration: none;">hit reply</a>. I read every email.
|
||||
</p>
|
||||
<p style="margin: 0; color: #555555; font-size: 15px; line-height: 1.7;">
|
||||
<strong>— Timo 👋</strong><br>
|
||||
<strong>- Timo 👋</strong><br>
|
||||
<span style="color: #888888; font-size: 13px;">Founder, QR Master</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -506,7 +506,7 @@ export async function sendAIFeatureLaunchEmail(email: string) {
|
||||
<a href="https://www.qrmaster.net/faq" style="color: #667eea; text-decoration: none;">Help</a>
|
||||
</p>
|
||||
<p style="margin: 0; color: #999999; font-size: 12px;">
|
||||
© 2026 QR Master. All rights reserved.
|
||||
© 2026 QR Master. All rights reserved.
|
||||
</p>
|
||||
<p style="margin: 15px 0 0 0; color: #aaaaaa; font-size: 11px;">
|
||||
You received this because you subscribed to AI feature launch notifications.
|
||||
@@ -535,29 +535,29 @@ export async function sendAIFeatureLaunchEmail(email: string) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SMTP Transport (nodemailer) — used for retention / welcome emails
|
||||
// SMTP Transport (nodemailer) - used for retention / welcome emails
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createSmtpTransport() {
|
||||
const host = process.env.SMTP_HOST || 'smtp.qrmaster.net';
|
||||
const port = parseInt(process.env.SMTP_PORT || '465', 10);
|
||||
const user = process.env.SMTP_USER || 'timo@qrmaster.net';
|
||||
const pass = process.env.SMTP_PASS?.trim();
|
||||
|
||||
if (!pass) {
|
||||
throw new Error('SMTP_PASS is missing. Configure SMTP credentials before sending welcome or retention emails.');
|
||||
}
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
});
|
||||
}
|
||||
function createSmtpTransport() {
|
||||
const host = process.env.SMTP_HOST || 'smtp.qrmaster.net';
|
||||
const port = parseInt(process.env.SMTP_PORT || '465', 10);
|
||||
const user = process.env.SMTP_USER || 'timo@qrmaster.net';
|
||||
const pass = process.env.SMTP_PASS?.trim();
|
||||
|
||||
if (!pass) {
|
||||
throw new Error('SMTP_PASS is missing. Configure SMTP credentials before sending welcome or retention emails.');
|
||||
}
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://www.qrmaster.net';
|
||||
|
||||
@@ -638,7 +638,7 @@ function emailShell(headExtra: string, bodyContent: string): string {
|
||||
const dotGridPattern = `url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%221.5%22%20fill%3D%22%23C8A257%22%20fill-opacity%3D%220.18%22%2F%3E%3C%2Fsvg%3E)`;
|
||||
|
||||
/**
|
||||
* Welcome Email — sent immediately on signup (Day 0)
|
||||
* Welcome Email - sent immediately on signup (Day 0)
|
||||
*/
|
||||
export async function sendWelcomeEmail(email: string, name: string) {
|
||||
const transport = createSmtpTransport();
|
||||
@@ -797,7 +797,7 @@ export async function sendWelcomeEmail(email: string, name: string) {
|
||||
<td style="padding:40px 48px 56px;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:15px;color:${clr.textSoft};line-height:1.7;">
|
||||
I'm thrilled to have you on board. If you have any questions, feedback, or just want to share what you've created — hit reply. I'm reading every single email.
|
||||
I'm thrilled to have you on board. If you have any questions, feedback, or just want to share what you've created - hit reply. I'm reading every single email.
|
||||
</p>
|
||||
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-top:28px;">
|
||||
@@ -806,7 +806,7 @@ export async function sendWelcomeEmail(email: string, name: string) {
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width:56px; height:56px; background-color:#0B0D14; border-radius:50%; text-align:center; vertical-align:middle; border:2px solid ${clr.border}; box-shadow:0 4px 10px rgba(0,0,0,0.05);">
|
||||
<img src="${appUrl}/favicon1.png" width="32" height="32" alt="Timo" style="display:inline-block; vertical-align:middle; border-radius:50%; object-fit:cover;">
|
||||
<img src="${appUrl}/favicon1.png" width="32" height="32" alt="Timo" style="display:inline-block; vertical-align:middle; border-radius:50%; object-fit:cover;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -838,7 +838,7 @@ export async function sendWelcomeEmail(email: string, name: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Activation Nudge — sent on Day 3 if user has 0 QR codes
|
||||
* Activation Nudge - sent on Day 3 if user has 0 QR codes
|
||||
*/
|
||||
export async function sendActivationNudgeEmail(email: string, name: string) {
|
||||
const transport = createSmtpTransport();
|
||||
@@ -847,7 +847,7 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
|
||||
|
||||
const steps = [
|
||||
{ n: '01', label: 'Paste your URL', sub: 'or choose WiFi, vCard, Teams, and more' },
|
||||
{ n: '02', label: 'Customize the design', sub: 'colors, logo, frame label — optional' },
|
||||
{ n: '02', label: 'Customize the design', sub: 'colors, logo, frame label - optional' },
|
||||
{ n: '03', label: 'Download & use', sub: 'PNG for screen, SVG for print' },
|
||||
];
|
||||
|
||||
@@ -905,7 +905,7 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
|
||||
<td style="padding:40px 48px 0;">
|
||||
<p style="margin:0 0 8px;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.text};">
|
||||
Your 3 free dynamic QR codes are still there. Unused.<br>
|
||||
Your 3 free dynamic QR codes are still there. Unused.<br>
|
||||
Dynamic means: one code, update the link anytime, every scan tracked.
|
||||
</p>
|
||||
<p style="margin:16px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
@@ -969,13 +969,13 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
to: email,
|
||||
subject: "You haven't made one yet",
|
||||
subject: "Your 3 free codes are still sitting there",
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade Nudge — sent on Day 7 if user has ≥1 QR code and is still on FREE plan
|
||||
* Upgrade Nudge - sent on Day 7 if user has ≥1 QR code and is still on FREE plan
|
||||
*/
|
||||
export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount: number) {
|
||||
const transport = createSmtpTransport();
|
||||
@@ -990,18 +990,18 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
|
||||
},
|
||||
{
|
||||
label: 'Logo in QR code',
|
||||
free: '—',
|
||||
free: '-',
|
||||
pro: 'Your logo, centered',
|
||||
},
|
||||
{
|
||||
label: 'Brand colors',
|
||||
free: '—',
|
||||
pro: 'Any color',
|
||||
label: 'Module shapes',
|
||||
free: '-',
|
||||
pro: '4 shapes + eye styles',
|
||||
},
|
||||
{
|
||||
label: 'CSV export',
|
||||
free: '✓',
|
||||
pro: '✓',
|
||||
label: 'Scan detail',
|
||||
free: 'Total scans',
|
||||
pro: 'Device, location, time',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1039,13 +1039,13 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
|
||||
|
||||
<h1 style="margin:28px 0 0;font-family:'DM Serif Display',Georgia,serif;
|
||||
font-size:34px;font-weight:400;line-height:1.2;color:#FFFFFF;">
|
||||
${qrCount} of 3 free codes used,<br>
|
||||
You are out of slots,<br>
|
||||
<em style="color:${clr.gold};">${firstName}.</em>
|
||||
</h1>
|
||||
|
||||
<p style="margin:16px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:14px;color:rgba(255,255,255,0.45);">
|
||||
Free plan · Day 7
|
||||
Free plan · ${qrCount} of 3 dynamic codes active
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1055,11 +1055,11 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
|
||||
<td style="padding:40px 48px 0;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.text};">
|
||||
The free plan gives you 3 dynamic QR codes. That's enough to start — not enough to scale. When you hit the limit, every new campaign means replacing an old one.
|
||||
Three codes is enough to prove the idea. It is not enough to run two campaigns at once - from here, every new placement means switching off an old one.
|
||||
</p>
|
||||
<p style="margin:16px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.textSoft};">
|
||||
Pro removes the ceiling — and adds custom branding your free codes never have:
|
||||
Pro raises it to 50, and shows you which placement actually earned the scan:
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1113,7 +1113,7 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
|
||||
<tr>
|
||||
<td align="center" style="padding-top:12px;">
|
||||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:12px;
|
||||
color:${clr.textMuted};">Your free plan stays active — no pressure</span>
|
||||
color:${clr.textMuted};">Your free plan stays active - no pressure</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1140,15 +1140,20 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
to: email,
|
||||
subject: `You're ${qrCount >= 3 ? 'at' : `${3 - qrCount} away from`} the free limit`,
|
||||
subject: 'You just hit the free limit',
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 30-Day Nudge — sent on Day 30 if user has ≥1 QR code and is still on FREE plan
|
||||
* 30-Day Nudge - sent on Day 30 if user has ≥1 QR code and is still on FREE plan
|
||||
*/
|
||||
export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCount: number) {
|
||||
export async function sendThirtyDayNudgeEmail(
|
||||
email: string,
|
||||
name: string,
|
||||
qrCount: number,
|
||||
scanCount: number = 0
|
||||
) {
|
||||
const transport = createSmtpTransport();
|
||||
const pricingUrl = `${appUrl}/pricing`;
|
||||
const firstName = name.split(' ')[0];
|
||||
@@ -1187,15 +1192,15 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
|
||||
<td style="padding:40px 48px 0;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.text};">
|
||||
You've created ${qrCount} QR code${qrCount !== 1 ? 's' : ''} in your first month. That tells me you're actually using this — not just signing up to forget it.
|
||||
Your codes were scanned <em style="color:${clr.text};">${scanCount} time${scanCount !== 1 ? 's' : ''}</em> this month, across ${qrCount} code${qrCount !== 1 ? 's' : ''}. Someone stood in front of your printed material and used it.
|
||||
</p>
|
||||
<p style="margin:20px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.textSoft};">
|
||||
The one thing I hear most from Pro users who switched after a few weeks: <em style="color:${clr.text};">they wish they'd added their brand sooner.</em> Every code they printed on free didn't have their logo. Every flyer had a generic black pattern instead of their colors.
|
||||
What you cannot see yet is <em style="color:${clr.text};">which of them</em>. The free plan gives you a running total. It does not tell you which placement, which city, or which kind of device brought those scans in.
|
||||
</p>
|
||||
<p style="margin:20px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.textSoft};">
|
||||
Pro fixes that. Two things it unlocks that free never will:
|
||||
That is the difference between knowing a campaign worked and knowing which part of it did:
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1222,10 +1227,10 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
|
||||
</td>
|
||||
<td style="padding-left:14px;vertical-align:top;padding-top:2px;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:15px;font-weight:600;color:${clr.text};letter-spacing:-0.2px;">Your logo, inside every QR code</p>
|
||||
font-size:15px;font-weight:600;color:${clr.text};letter-spacing:-0.2px;">Which placement earned the scan</p>
|
||||
<p style="margin:6px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:14px;color:${clr.textSoft};line-height:1.6;">
|
||||
Upload once. Every code you make automatically carries your brand mark — menus, flyers, packaging, wherever.
|
||||
One code per flyer, poster or table tent, and the dashboard shows you which one people actually used. The others you can stop printing.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1252,10 +1257,10 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
|
||||
</td>
|
||||
<td style="padding-left:14px;vertical-align:top;padding-top:2px;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:15px;font-weight:600;color:${clr.text};letter-spacing:-0.2px;">Brand colors — not just black</p>
|
||||
font-size:15px;font-weight:600;color:${clr.text};letter-spacing:-0.2px;">Device and location, not just a total</p>
|
||||
<p style="margin:6px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:14px;color:${clr.textSoft};line-height:1.6;">
|
||||
Match your QR codes to your brand palette. Looks intentional. Scans the same.
|
||||
See whether scans came from phones at the venue or desktops later, and which cities they came from. Enough to decide where the next print run goes.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1277,14 +1282,14 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
|
||||
text-decoration:none;padding:18px 52px;border-radius:10px;
|
||||
font-family:'DM Sans',-apple-system,sans-serif;font-size:15px;
|
||||
font-weight:600;letter-spacing:0.3px;border:1px solid rgba(200,162,87,0.3);">
|
||||
Add my brand to QR codes →
|
||||
See the full breakdown →
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding-top:12px;">
|
||||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:12px;
|
||||
color:${clr.textMuted};">Your existing codes keep working — nothing breaks</span>
|
||||
color:${clr.textMuted};">Your existing codes and their scan history stay exactly as they are</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1311,7 +1316,122 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
to: email,
|
||||
subject: `${firstName}, a month of QR codes — one upgrade worth making`,
|
||||
subject: `${firstName}, your codes were scanned ${scanCount} time${scanCount !== 1 ? 's' : ''} this month`,
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* First Scan - sent the day after a user's first ever scan.
|
||||
*
|
||||
* The only email in the sequence whose trigger is not the calendar. Everything
|
||||
* else fires on day 3, 7 or 30 whether or not anything happened; this one fires
|
||||
* because something did. It is also the moment the positioning first pays off -
|
||||
* a printed thing became a measurement - so it deliberately sells nothing. The
|
||||
* CTA goes to the user's own data, not to pricing.
|
||||
*/
|
||||
export async function sendFirstScanEmail(
|
||||
email: string,
|
||||
name: string,
|
||||
scan: { qrTitle: string; device?: string | null; country?: string | null; ts: Date }
|
||||
) {
|
||||
const transport = createSmtpTransport();
|
||||
const firstName = name.split(' ')[0];
|
||||
const analyticsUrl = `${appUrl}/analytics`;
|
||||
|
||||
const time = scan.ts.toLocaleTimeString('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
|
||||
// Only state what was actually recorded. Device and country can be missing,
|
||||
// and inventing a clean sentence around absent data is exactly the habit this
|
||||
// product is positioned against.
|
||||
const details = [
|
||||
`at ${time} UTC`,
|
||||
scan.device ? `on ${scan.device.toLowerCase()}` : null,
|
||||
scan.country ? `from ${scan.country}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
const html = emailShell('', `
|
||||
<tr>
|
||||
<td style="background-color:${clr.header};background-image:${dotGridPattern};
|
||||
background-size:24px 24px;padding:44px 48px 40px;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:12px;font-weight:600;letter-spacing:1.6px;
|
||||
text-transform:uppercase;color:${clr.gold};">QR Master</p>
|
||||
<h1 style="margin:28px 0 0;font-family:'DM Serif Display',Georgia,serif;
|
||||
font-size:34px;font-weight:400;line-height:1.2;color:#FFFFFF;">
|
||||
Someone scanned it,<br>
|
||||
<em style="color:${clr.gold};">${firstName}.</em>
|
||||
</h1>
|
||||
<p style="margin:16px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:14px;line-height:1.6;color:rgba(255,255,255,0.62);">
|
||||
First scan · ${scan.qrTitle}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:40px 48px 0;">
|
||||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.text};">
|
||||
Your code "${scan.qrTitle}" was scanned for the first time ${details}.
|
||||
</p>
|
||||
<p style="margin:20px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.textSoft};">
|
||||
Somebody stood in front of something you made and used it. That is the
|
||||
part a static code can never tell you.
|
||||
</p>
|
||||
<p style="margin:20px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:16px;line-height:1.75;color:${clr.textSoft};">
|
||||
Every scan from here on is counted. In a week or two you will be able to
|
||||
see when they cluster - and whether the spot you chose was the right one.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:32px 48px 48px;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="background-color:${clr.ctaBg};border-radius:6px;">
|
||||
<a href="${analyticsUrl}"
|
||||
style="display:inline-block;padding:15px 30px;
|
||||
font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:15px;font-weight:600;color:${clr.ctaText};
|
||||
text-decoration:none;letter-spacing:-0.1px;">
|
||||
See the scans →
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin:24px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:15px;line-height:1.7;color:${clr.textSoft};">
|
||||
If the destination needs to change, you can edit it without touching the
|
||||
printed code. That is what it is for.
|
||||
</p>
|
||||
<p style="margin:28px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:15px;line-height:1.7;color:${clr.text};">
|
||||
Timo
|
||||
</p>
|
||||
<p style="margin:2px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||||
font-size:12px;font-weight:600;letter-spacing:1.2px;
|
||||
text-transform:uppercase;color:${clr.textMuted};">
|
||||
Founder, QR Master
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
to: email,
|
||||
subject: 'Your QR code was just scanned for the first time',
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user