1318 lines
65 KiB
TypeScript
1318 lines
65 KiB
TypeScript
/**
|
||
* Email Templates - CRO-Optimized
|
||
*
|
||
* What's improved (based on email marketing best practices):
|
||
*
|
||
* Subject Lines:
|
||
* - More specific with urgency/outcome ("Expires in 1 Hour")
|
||
* - Emoji for visual attention in inbox
|
||
* - Clear value proposition
|
||
*
|
||
* Design & Copy:
|
||
* - Personal sender name ("Timo from QR Master")
|
||
* - Storytelling instead of feature lists
|
||
* - Trust elements (security badges, timelines)
|
||
* - Clear CTAs with action verbs ("Reset My Password →")
|
||
* - Objection handling built-in ("Didn't request this?")
|
||
* - Mobile-optimized (shorter paragraphs, more whitespace)
|
||
*
|
||
* How to measure if this works for YOUR audience:
|
||
* 1. Track baseline metrics (open rate, click rate, conversions)
|
||
* 2. A/B test old vs new templates
|
||
* 3. Measure with real users (minimum 100 emails per variant)
|
||
*
|
||
* No guarantees - only your data will tell what works for QR Master users.
|
||
*/
|
||
|
||
import { Resend } from 'resend';
|
||
import nodemailer from 'nodemailer';
|
||
|
||
// Use a placeholder during build time, real key at runtime
|
||
const resendKey = process.env.RESEND_API_KEY || 're_placeholder_for_build';
|
||
const resend = new Resend(resendKey);
|
||
|
||
// Rate limiter for Resend Free Tier (1 email per second)
|
||
let lastEmailSent = 0;
|
||
const MIN_EMAIL_INTERVAL = 1000; // 1 second in milliseconds
|
||
|
||
async function waitForRateLimit() {
|
||
const now = Date.now();
|
||
const timeSinceLastEmail = now - lastEmailSent;
|
||
|
||
if (timeSinceLastEmail < MIN_EMAIL_INTERVAL) {
|
||
const waitTime = MIN_EMAIL_INTERVAL - timeSinceLastEmail;
|
||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||
}
|
||
|
||
lastEmailSent = Date.now();
|
||
}
|
||
|
||
/**
|
||
* Password Reset Email - Security focused with clear urgency
|
||
*/
|
||
export async function sendPasswordResetEmail(email: string, resetToken: string) {
|
||
await waitForRateLimit();
|
||
|
||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3050';
|
||
const resetUrl = `${appUrl}/reset-password?token=${resetToken}`;
|
||
|
||
try {
|
||
await resend.emails.send({
|
||
from: 'QR Master Security <noreply@qrmaster.net>',
|
||
replyTo: 'support@qrmaster.net',
|
||
to: email,
|
||
subject: '🔐 Reset Your QR Master Password (Expires in 1 Hour)',
|
||
html: `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Reset Your Password - QR Master</title>
|
||
</head>
|
||
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f4;">
|
||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 40px 20px;">
|
||
<tr>
|
||
<td align="center">
|
||
<!-- Main Container -->
|
||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.08);">
|
||
|
||
<!-- Header with Security Badge -->
|
||
<tr>
|
||
<td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 50px 40px; text-align: center;">
|
||
<div style="display: inline-block; background-color: rgba(255,255,255,0.2); padding: 12px 24px; border-radius: 50px; margin-bottom: 20px;">
|
||
<span style="color: #ffffff; font-size: 14px; font-weight: 600; letter-spacing: 1px;">🔒 SECURITY ALERT</span>
|
||
</div>
|
||
<h1 style="margin: 0; color: #ffffff; font-size: 32px; font-weight: bold; line-height: 1.2;">Password Reset Request</h1>
|
||
<p style="margin: 15px 0 0 0; color: rgba(255,255,255,0.9); font-size: 16px;">Someone requested to reset your password</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Content Section -->
|
||
<tr>
|
||
<td style="padding: 50px 40px;">
|
||
<p style="margin: 0 0 20px 0; color: #333333; font-size: 16px; line-height: 1.6;">
|
||
Hi there,
|
||
</p>
|
||
|
||
<p style="margin: 0 0 25px 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||
We received a request to reset the password for your QR Master account. If this was you, click the button below to create a new password:
|
||
</p>
|
||
|
||
<!-- Primary CTA Button -->
|
||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 35px 0;">
|
||
<tr>
|
||
<td align="center">
|
||
<a href="${resetUrl}" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; text-decoration: none; padding: 18px 50px; border-radius: 8px; font-size: 17px; font-weight: 700; display: inline-block; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);">
|
||
🔐 Reset My Password →
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<!-- Security Info Box -->
|
||
<div style="background: linear-gradient(135deg, rgba(255, 107, 107, 0.08) 0%, rgba(255, 148, 148, 0.08) 100%); border-left: 4px solid #ff6b6b; padding: 20px 25px; margin: 35px 0; border-radius: 6px;">
|
||
<p style="margin: 0 0 12px 0; color: #d63031; font-size: 15px; font-weight: 700;">
|
||
⏱️ This link expires in 60 minutes
|
||
</p>
|
||
<p style="margin: 0; color: #666666; font-size: 14px; line-height: 1.6;">
|
||
For your security, this password reset link will only work once and expires after 1 hour. If you need a new link, you can request another one anytime.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Alternative Link -->
|
||
<p style="margin: 30px 0 15px 0; color: #888888; font-size: 14px; line-height: 1.5;">
|
||
🔗 Button not working? Copy and paste this link into your browser:
|
||
</p>
|
||
|
||
<p style="margin: 0 0 30px 0; padding: 18px 20px; background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; word-break: break-all;">
|
||
<a href="${resetUrl}" style="color: #667eea; text-decoration: none; font-size: 13px; font-family: monospace;">${resetUrl}</a>
|
||
</p>
|
||
|
||
<!-- Didn't Request This? -->
|
||
<div style="background-color: #f8f9fa; padding: 25px; border-radius: 8px; margin-top: 35px;">
|
||
<p style="margin: 0 0 12px 0; color: #333333; font-size: 15px; font-weight: 600;">
|
||
❓ Didn't request this?
|
||
</p>
|
||
<p style="margin: 0; color: #666666; font-size: 14px; line-height: 1.6;">
|
||
✅ You can safely ignore this email. Your password won't be changed unless you click the button above. If you're concerned about your account security, please contact us immediately at <a href="mailto:support@qrmaster.net" style="color: #667eea; text-decoration: none;">support@qrmaster.net</a>
|
||
</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Footer -->
|
||
<tr>
|
||
<td style="background-color: #f8f9fa; padding: 35px 40px; border-top: 1px solid #e9ecef;">
|
||
<table width="100%" cellpadding="0" cellspacing="0">
|
||
<tr>
|
||
<td align="center">
|
||
<p style="margin: 0 0 15px 0; color: #333333; font-size: 16px; font-weight: 600;">
|
||
QR Master
|
||
</p>
|
||
<p style="margin: 0 0 8px 0; color: #888888; font-size: 13px;">
|
||
Secure QR Code Analytics & Management
|
||
</p>
|
||
<p style="margin: 0; color: #999999; font-size: 12px;">
|
||
© 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.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`,
|
||
});
|
||
|
||
console.log('Password reset email sent successfully to:', email);
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error('Error sending password reset email:', error);
|
||
throw new Error('Failed to send password reset email');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Newsletter Welcome Email - Value-focused with clear expectations
|
||
*/
|
||
export async function sendNewsletterWelcomeEmail(email: string) {
|
||
await waitForRateLimit();
|
||
|
||
try {
|
||
await resend.emails.send({
|
||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||
replyTo: 'support@qrmaster.net',
|
||
to: email,
|
||
subject: '🎉 You\'re In! Here\'s What Happens Next (AI QR Features)',
|
||
html: `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Welcome to QR Master AI Waitlist</title>
|
||
</head>
|
||
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f4;">
|
||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 40px 20px;">
|
||
<tr>
|
||
<td align="center">
|
||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.08);">
|
||
|
||
<!-- Hero Header -->
|
||
<tr>
|
||
<td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 50px 40px; text-align: center;">
|
||
<div style="display: inline-block; background-color: rgba(255,255,255,0.2); padding: 10px 20px; border-radius: 50px; margin-bottom: 20px;">
|
||
<span style="color: #ffffff; font-size: 13px; font-weight: 600; letter-spacing: 1px;">✨ EARLY ACCESS</span>
|
||
</div>
|
||
<h1 style="margin: 0; color: #ffffff; font-size: 36px; font-weight: bold; line-height: 1.2;">You're on the List! 🚀</h1>
|
||
<p style="margin: 20px 0 0 0; color: rgba(255,255,255,0.95); font-size: 18px; line-height: 1.5;">Get ready for AI-powered QR codes that work smarter, not harder</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Personal Note -->
|
||
<tr>
|
||
<td style="padding: 45px 40px 35px 40px;">
|
||
<p style="margin: 0 0 20px 0; color: #333333; font-size: 16px; line-height: 1.7;">
|
||
Hey there! 👋
|
||
</p>
|
||
|
||
<p style="margin: 0 0 25px 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||
Thanks for joining the waitlist for QR Master's AI-powered features. You're among the <strong>first to know</strong> when we launch something that'll completely change how you create and manage QR codes.
|
||
</p>
|
||
|
||
<p style="margin: 0 0 35px 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||
No fluff, no spam. Just a heads-up when these features go live.
|
||
</p>
|
||
|
||
<!-- What's Coming Section -->
|
||
<div style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%); border-left: 4px solid #667eea; padding: 30px 30px; margin: 35px 0; border-radius: 8px;">
|
||
<h2 style="margin: 0 0 20px 0; color: #667eea; font-size: 20px; font-weight: 700;">
|
||
🎯 What You Can Expect
|
||
</h2>
|
||
|
||
<!-- Feature 1 -->
|
||
<div style="margin-bottom: 20px;">
|
||
<h3 style="margin: 0 0 8px 0; color: #333333; font-size: 16px; font-weight: 700;">
|
||
⚡ Smart QR Generation
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
AI analyzes your content and automatically suggests the optimal QR design, colors, and format for maximum scans. No more guessing what works.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Feature 2 -->
|
||
<div style="margin-bottom: 20px;">
|
||
<h3 style="margin: 0 0 8px 0; color: #333333; font-size: 16px; font-weight: 700;">
|
||
📊 Predictive Analytics
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
See scan forecasts, detect unusual patterns, and ask questions about your data in plain English. Your analytics become conversations, not spreadsheets.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Feature 3 -->
|
||
<div style="margin-bottom: 20px;">
|
||
<h3 style="margin: 0 0 8px 0; color: #333333; font-size: 16px; font-weight: 700;">
|
||
🤖 Auto-Organization
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
Your QR codes automatically get categorized, tagged, and organized. Spend less time managing, more time creating.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Feature 4 -->
|
||
<div>
|
||
<h3 style="margin: 0 0 8px 0; color: #333333; font-size: 16px; font-weight: 700;">
|
||
🎨 AI Design Studio
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
Generate unique, on-brand QR code designs in seconds. Just describe what you want, AI handles the rest.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Timeline -->
|
||
<div style="background-color: #fff3cd; border: 1px solid #ffc107; padding: 25px; border-radius: 8px; margin: 35px 0;">
|
||
<p style="margin: 0 0 12px 0; color: #856404; font-size: 15px; font-weight: 700;">
|
||
⏰ 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.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Meanwhile CTA -->
|
||
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 8px; margin: 35px 0; text-align: center;">
|
||
<h3 style="margin: 0 0 15px 0; color: #333333; font-size: 18px; font-weight: 700;">
|
||
💡 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.
|
||
</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 →
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Personal Sign-off -->
|
||
<p style="margin: 35px 0 0 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||
💌 Thanks for trusting us with your inbox. We'll make it worth it.
|
||
</p>
|
||
|
||
<p style="margin: 20px 0 0 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||
<strong>— Timo 👋</strong><br>
|
||
<span style="color: #888888; font-size: 14px;">Founder, QR Master</span>
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Footer -->
|
||
<tr>
|
||
<td style="background-color: #f8f9fa; padding: 35px 40px; border-top: 1px solid #e9ecef;">
|
||
<table width="100%" cellpadding="0" cellspacing="0">
|
||
<tr>
|
||
<td align="center">
|
||
<p style="margin: 0 0 8px 0; color: #888888; font-size: 13px;">
|
||
<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.
|
||
</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.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`,
|
||
});
|
||
|
||
console.log('Newsletter welcome email sent successfully to:', email);
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error('Error sending newsletter welcome email:', error);
|
||
throw new Error('Failed to send newsletter welcome email');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* AI Feature Launch Email - Excitement + clear CTA
|
||
*/
|
||
export async function sendAIFeatureLaunchEmail(email: string) {
|
||
await waitForRateLimit();
|
||
|
||
try {
|
||
await resend.emails.send({
|
||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||
replyTo: 'support@qrmaster.net',
|
||
to: email,
|
||
subject: '🚀 They\'re Live! Your AI QR Features Are Ready',
|
||
html: `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>AI Features Are Live - QR Master</title>
|
||
</head>
|
||
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f4;">
|
||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4; padding: 40px 20px;">
|
||
<tr>
|
||
<td align="center">
|
||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.08);">
|
||
|
||
<!-- Celebration Header -->
|
||
<tr>
|
||
<td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px 30px; text-align: center;">
|
||
<div style="font-size: 32px; margin-bottom: 10px;">🎉</div>
|
||
<div style="display: inline-block; background-color: rgba(255,255,255,0.2); padding: 6px 14px; border-radius: 50px; margin-bottom: 12px;">
|
||
<span style="color: #ffffff; font-size: 11px; font-weight: 600; letter-spacing: 1px;">✨ NOW LIVE</span>
|
||
</div>
|
||
<h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: bold; line-height: 1.2;">AI Features Are Here! 🚀</h1>
|
||
<p style="margin: 10px 0 0 0; color: rgba(255,255,255,0.95); font-size: 14px; line-height: 1.4;">Ready to use in your dashboard</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Main Content -->
|
||
<tr>
|
||
<td style="padding: 50px 40px;">
|
||
|
||
<!-- Opening -->
|
||
<p style="margin: 0 0 25px 0; color: #333333; font-size: 17px; line-height: 1.7;">
|
||
Remember when you signed up to be notified about our AI features?
|
||
</p>
|
||
|
||
<p style="margin: 0 0 25px 0; color: #333333; font-size: 17px; line-height: 1.7; font-weight: 700;">
|
||
Well, today's that day. 🎯
|
||
</p>
|
||
|
||
<p style="margin: 0 0 35px 0; color: #555555; font-size: 16px; line-height: 1.7;">
|
||
We've been building, testing, and polishing these features for months. And they're finally ready for you to use. Right now. No beta, no waitlist, no BS.
|
||
</p>
|
||
|
||
<!-- What's New Highlight Box -->
|
||
<div style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); border: 2px solid #667eea; padding: 35px 30px; margin: 40px 0; border-radius: 10px;">
|
||
<h2 style="margin: 0 0 25px 0; color: #667eea; font-size: 22px; font-weight: 700; text-align: center;">
|
||
✨ What's New in Your Dashboard
|
||
</h2>
|
||
|
||
<!-- Feature Cards -->
|
||
<div style="background-color: #ffffff; padding: 25px; margin-bottom: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
||
<h3 style="margin: 0 0 10px 0; color: #333333; font-size: 17px; font-weight: 700;">
|
||
⚡ Smart QR Generation
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
AI analyzes your content and suggests optimal designs automatically. Just paste your link, we handle the optimization.
|
||
</p>
|
||
</div>
|
||
|
||
<div style="background-color: #ffffff; padding: 25px; margin-bottom: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
||
<h3 style="margin: 0 0 10px 0; color: #333333; font-size: 17px; font-weight: 700;">
|
||
📊 Predictive Analytics
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
Ask questions about your scan data in plain English. "Which campaign performed best last month?" Done.
|
||
</p>
|
||
</div>
|
||
|
||
<div style="background-color: #ffffff; padding: 25px; margin-bottom: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
||
<h3 style="margin: 0 0 10px 0; color: #333333; font-size: 17px; font-weight: 700;">
|
||
🤖 Auto-Organization
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
QR codes automatically categorized and tagged. Your library stays organized without manual work.
|
||
</p>
|
||
</div>
|
||
|
||
<div style="background-color: #ffffff; padding: 25px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
||
<h3 style="margin: 0 0 10px 0; color: #333333; font-size: 17px; font-weight: 700;">
|
||
🎨 AI Design Studio
|
||
</h3>
|
||
<p style="margin: 0; color: #666666; font-size: 15px; line-height: 1.6;">
|
||
Generate custom QR designs by describing what you want. "Make it modern and blue" → Done in 3 seconds.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- How to Access -->
|
||
<div style="background-color: #fff3cd; border: 2px solid #ffc107; padding: 30px; border-radius: 10px; margin: 40px 0;">
|
||
<h3 style="margin: 0 0 15px 0; color: #856404; font-size: 18px; font-weight: 700;">
|
||
🎯 How to Get Started
|
||
</h3>
|
||
<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>📈 Check your Analytics tab for the new AI query interface</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<!-- Primary CTA -->
|
||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 45px 0 40px 0;">
|
||
<tr>
|
||
<td align="center">
|
||
<a href="https://www.qrmaster.net/create" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; text-decoration: none; padding: 20px 60px; border-radius: 10px; font-size: 18px; font-weight: 700; display: inline-block; box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);">
|
||
✨ Try AI Features Now →
|
||
</a>
|
||
<p style="margin: 20px 0 0 0; color: #888888; font-size: 13px;">
|
||
✅ Available on all plans • ⚡ No extra setup required
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<!-- 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.
|
||
</p>
|
||
<p style="margin: 0; color: #555555; font-size: 15px; line-height: 1.7;">
|
||
<strong>— Timo 👋</strong><br>
|
||
<span style="color: #888888; font-size: 13px;">Founder, QR Master</span>
|
||
</p>
|
||
</div>
|
||
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Footer -->
|
||
<tr>
|
||
<td style="background-color: #f8f9fa; padding: 35px 40px; border-top: 1px solid #e9ecef;">
|
||
<table width="100%" cellpadding="0" cellspacing="0">
|
||
<tr>
|
||
<td align="center">
|
||
<p style="margin: 0 0 8px 0; color: #888888; font-size: 13px;">
|
||
<a href="https://www.qrmaster.net" style="color: #667eea; text-decoration: none;">www.qrmaster.net</a> •
|
||
<a href="https://www.qrmaster.net/dashboard" style="color: #667eea; text-decoration: none;">Dashboard</a> •
|
||
<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.
|
||
</p>
|
||
<p style="margin: 15px 0 0 0; color: #aaaaaa; font-size: 11px;">
|
||
You received this because you subscribed to AI feature launch notifications.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`,
|
||
});
|
||
|
||
console.log('AI feature launch email sent successfully to:', email);
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error('Error sending AI feature launch email:', error);
|
||
throw new Error('Failed to send AI feature launch email');
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 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,
|
||
},
|
||
});
|
||
}
|
||
|
||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://www.qrmaster.net';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Shared design tokens (email-safe inline styles)
|
||
// ---------------------------------------------------------------------------
|
||
const clr = {
|
||
bg: '#F5F2EC', // warm parchment wrapper
|
||
card: '#FFFFFF',
|
||
header: '#0B0D14', // near-black header
|
||
headerAccent: '#1A1D2E',
|
||
gold: '#C8A257', // warm gold accent
|
||
goldDim: '#A07E3A',
|
||
text: '#1A1A1A',
|
||
textSoft: '#5A5A5A',
|
||
textMuted: '#909090',
|
||
border: '#E8E3D8',
|
||
pillBg: '#F0EDE5',
|
||
ctaBg: '#0B0D14',
|
||
ctaText: '#FFFFFF',
|
||
};
|
||
|
||
const webFontHead = `
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:wght@300;400;500;600&display=swap');
|
||
.dm-serif { font-family: 'DM Serif Display', Georgia, 'Times New Roman', serif !important; }
|
||
.dm-sans { font-family: 'DM Sans', -apple-system, 'Helvetica Neue', Arial, sans-serif !important; }
|
||
</style>`;
|
||
|
||
function emailShell(headExtra: string, bodyContent: string): string {
|
||
return `<!DOCTYPE html>
|
||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light">
|
||
<meta name="supported-color-schemes" content="light">
|
||
${webFontHead}
|
||
${headExtra}
|
||
</head>
|
||
<body style="margin:0;padding:0;background-color:${clr.bg};-webkit-text-size-adjust:100%;mso-line-height-rule:exactly;">
|
||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
|
||
style="background-color:${clr.bg};padding:40px 16px 60px;">
|
||
<tr><td align="center">
|
||
|
||
<!-- Email card -->
|
||
<table role="presentation" width="560" cellpadding="0" cellspacing="0" border="0"
|
||
style="max-width:560px;width:100%;background-color:${clr.card};border-radius:16px;
|
||
overflow:hidden;box-shadow:0 8px 40px rgba(0,0,0,0.12);">
|
||
${bodyContent}
|
||
</table>
|
||
|
||
<!-- Footer text -->
|
||
<table role="presentation" width="560" cellpadding="0" cellspacing="0" border="0"
|
||
style="max-width:560px;width:100%;margin-top:28px;">
|
||
<tr>
|
||
<td style="text-align:center;padding:0 20px;">
|
||
<p style="margin:0 0 6px;font-family:'DM Sans',-apple-system,sans-serif;font-size:12px;color:${clr.textMuted};">
|
||
<a href="${appUrl}" style="color:${clr.gold};text-decoration:none;font-weight:500;">www.qrmaster.net</a>
|
||
·
|
||
<a href="mailto:support@qrmaster.net" style="color:${clr.textMuted};text-decoration:none;">support@qrmaster.net</a>
|
||
</p>
|
||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;color:#B0A898;">
|
||
© 2026 QR Master. You're receiving this because you created an account.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
</td></tr>
|
||
</table>
|
||
</body>
|
||
</html>`;
|
||
}
|
||
|
||
// Dot-grid SVG encoded as data URI for header backgrounds
|
||
// Safe URL-encoded SVG without problematic internal quotes
|
||
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)
|
||
*/
|
||
export async function sendWelcomeEmail(email: string, name: string) {
|
||
const transport = createSmtpTransport();
|
||
const createUrl = `${appUrl}/create`;
|
||
const firstName = name.split(' ')[0];
|
||
|
||
const html = emailShell('', `
|
||
<!-- ── HEADER ── -->
|
||
<tr>
|
||
<td style="background-color:${clr.card};background-image:${dotGridPattern};
|
||
background-size:24px 24px;padding:60px 48px 50px;text-align:center;">
|
||
|
||
<!-- Wordmark Badge -->
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" align="center">
|
||
<tr>
|
||
<td style="border:1px solid rgba(200,162,87,0.4);border-radius:6px;
|
||
padding:6px 16px;background-color:rgba(200,162,87,0.05);">
|
||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:10px;
|
||
font-weight:700;letter-spacing:3px;color:${clr.gold};
|
||
text-transform:uppercase;">QR Master</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<!-- Headline -->
|
||
<h1 style="margin:36px 0 0;font-family:'DM Serif Display',Georgia,serif;
|
||
font-size:42px;font-weight:400;line-height:1.1;color:${clr.text};
|
||
letter-spacing:-0.5px;">
|
||
Welcome,<br>
|
||
<em style="color:${clr.gold};font-style:italic;">${firstName}.</em>
|
||
</h1>
|
||
|
||
<!-- Thin gold divider -->
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
|
||
align="center" style="margin-top:32px;">
|
||
<tr>
|
||
<td style="width:40px;height:1px;background-color:${clr.gold};opacity:0.6;"></td>
|
||
<td style="width:12px;"></td>
|
||
<td style="width:6px;height:6px;background-color:${clr.gold};border-radius:50%;
|
||
opacity:0.9;"></td>
|
||
<td style="width:12px;"></td>
|
||
<td style="width:40px;height:1px;background-color:${clr.gold};opacity:0.6;"></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<p style="margin:24px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:15px;color:${clr.textSoft};letter-spacing:0.3px;">
|
||
The physical world is now your canvas.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── HERO IMAGE ── -->
|
||
<tr>
|
||
<td style="padding: 0; text-align: center; background-color: ${clr.card};">
|
||
<img src="${appUrl}/email-hero-light.png" width="560" style="display:block;width:100%;max-width:560px;height:auto;border-bottom:3px solid ${clr.gold};" alt="Beautiful QR Code Experience">
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── BODY ── -->
|
||
<tr>
|
||
<td style="padding:56px 48px 0;">
|
||
<p style="margin:0 0 24px;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:16px;line-height:1.8;color:${clr.text};">
|
||
Let's be honest: most QR codes are static, look terrible, and break the moment you change a link. We built QR Master to fix that.
|
||
</p>
|
||
<p style="margin:0 0 24px;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:16px;line-height:1.8;color:${clr.text};">
|
||
Your account is fully activated. You now have the power to create beautiful, dynamic QR codes that adapt to your brand, never expire, and track every single scan (device, location, and time).
|
||
</p>
|
||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:16px;line-height:1.8;color:${clr.textSoft};">
|
||
To give you the perfect start, we've loaded your account with everything you need:
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── STATS STRIP ── -->
|
||
<tr>
|
||
<td style="padding:16px 48px 32px;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<!-- Stat 1 -->
|
||
<td style="padding:12px 0;text-align:center;width:31%;">
|
||
<div style="font-family:'DM Serif Display',Georgia,serif;font-size:28px;
|
||
color:${clr.text};line-height:1;">3</div>
|
||
<div style="font-family:'DM Sans',-apple-system,sans-serif;font-size:10px;
|
||
font-weight:700;color:${clr.textMuted};letter-spacing:1px;margin-top:8px;
|
||
text-transform:uppercase;">
|
||
Free Codes
|
||
</div>
|
||
</td>
|
||
<td style="width:3.5%;"></td>
|
||
<!-- Stat 2 -->
|
||
<td style="padding:12px 0;text-align:center;width:31%;">
|
||
<div style="font-family:'DM Serif Display',Georgia,serif;font-size:28px;
|
||
color:${clr.text};line-height:1;">90<span style="font-size:18px;">s</span></div>
|
||
<div style="font-family:'DM Sans',-apple-system,sans-serif;font-size:10px;
|
||
font-weight:700;color:${clr.textMuted};letter-spacing:1px;margin-top:8px;
|
||
text-transform:uppercase;">
|
||
To Create
|
||
</div>
|
||
</td>
|
||
<td style="width:3.5%;"></td>
|
||
<!-- Stat 3 -->
|
||
<td style="padding:12px 0;text-align:center;width:31%;">
|
||
<div style="font-family:'DM Sans',-apple-system,sans-serif;font-size:28px;
|
||
color:${clr.text};line-height:1;">∞</div>
|
||
<div style="font-family:'DM Sans',-apple-system,sans-serif;font-size:10px;
|
||
font-weight:700;color:${clr.textMuted};letter-spacing:1px;margin-top:8px;
|
||
text-transform:uppercase;">
|
||
No Expiry
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── CTA ── -->
|
||
<tr>
|
||
<td style="padding:12px 48px 56px;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td align="center">
|
||
<a href="${createUrl}"
|
||
style="display:inline-block;background-color:${clr.ctaBg};color:${clr.ctaText};
|
||
text-decoration:none;padding:18px 48px;border-radius:8px;
|
||
font-family:'DM Sans',-apple-system,sans-serif;font-size:16px;
|
||
font-weight:600;letter-spacing:0.5px;
|
||
border:1px solid rgba(200,162,87,0.5);
|
||
box-shadow:0 6px 20px rgba(11,13,20,0.15);">
|
||
Design your first QR code →
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center" style="padding-top:16px;">
|
||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:13px;
|
||
color:${clr.textMuted};">No credit card required. Zero commitment.</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── DIVIDER ── -->
|
||
<tr>
|
||
<td style="padding:0 48px;">
|
||
<div style="border-top:1px solid ${clr.border};"></div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── SIGN-OFF ── -->
|
||
<tr>
|
||
<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.
|
||
</p>
|
||
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin-top:28px;">
|
||
<tr>
|
||
<td style="padding-right:16px;">
|
||
<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;">
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
<td>
|
||
<p style="margin:0;font-family:'DM Serif Display',Georgia,serif;
|
||
font-size:24px;font-style:italic;color:${clr.text};line-height:1.2;">
|
||
Timo
|
||
</p>
|
||
<p style="margin:4px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:11px;font-weight:700;color:${clr.textMuted};
|
||
letter-spacing:1.5px;text-transform:uppercase;">
|
||
Founder, QR Master
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
`);
|
||
|
||
await transport.sendMail({
|
||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||
replyTo: 'support@qrmaster.net',
|
||
to: email,
|
||
subject: 'Your QR Master account is ready',
|
||
html,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Activation Nudge — sent on Day 3 if user has 0 QR codes
|
||
*/
|
||
export async function sendActivationNudgeEmail(email: string, name: string) {
|
||
const transport = createSmtpTransport();
|
||
const createUrl = `${appUrl}/create`;
|
||
const firstName = name.split(' ')[0];
|
||
|
||
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: '03', label: 'Download & use', sub: 'PNG for screen, SVG for print' },
|
||
];
|
||
|
||
const stepsHtml = steps.map(s => `
|
||
<tr>
|
||
<td style="padding:16px 0;border-bottom:1px solid ${clr.border};">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td style="width:36px;vertical-align:top;padding-top:2px;">
|
||
<span style="font-family:'DM Serif Display',Georgia,serif;font-size:13px;
|
||
color:${clr.gold};font-style:italic;">${s.n}</span>
|
||
</td>
|
||
<td>
|
||
<div style="font-family:'DM Sans',-apple-system,sans-serif;font-size:15px;
|
||
font-weight:600;color:${clr.text};">${s.label}</div>
|
||
<div style="font-family:'DM Sans',-apple-system,sans-serif;font-size:13px;
|
||
color:${clr.textMuted};margin-top:2px;">${s.sub}</div>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>`).join('');
|
||
|
||
const html = emailShell('', `
|
||
<!-- ── HEADER ── -->
|
||
<tr>
|
||
<td style="background-color:${clr.header};background-image:${dotGridPattern};
|
||
background-size:24px 24px;padding:44px 48px 40px;">
|
||
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||
<tr>
|
||
<td style="border:1px solid rgba(200,162,87,0.35);border-radius:8px;padding:7px 18px;">
|
||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;
|
||
font-weight:600;letter-spacing:3px;color:${clr.gold};
|
||
text-transform:uppercase;">QR Master</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<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;">
|
||
You haven't made one yet,<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);letter-spacing:0.2px;">
|
||
3 days since signup · 0 QR codes created
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── BODY ── -->
|
||
<tr>
|
||
<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>
|
||
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;
|
||
font-size:15px;line-height:1.75;color:${clr.textSoft};">
|
||
Here's all it takes:
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── STEPS ── -->
|
||
<tr>
|
||
<td style="padding:8px 48px 0;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
${stepsHtml}
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── CTA ── -->
|
||
<tr>
|
||
<td style="padding:36px 48px 44px;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td align="center">
|
||
<a href="${createUrl}"
|
||
style="display:inline-block;background-color:${clr.ctaBg};color:${clr.ctaText};
|
||
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);">
|
||
Make my first QR code →
|
||
</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};">Free forever · no card needed</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── DIVIDER + SIGN-OFF ── -->
|
||
<tr><td style="padding:0 48px;"><div style="border-top:1px solid ${clr.border};"></div></td></tr>
|
||
<tr>
|
||
<td style="padding:28px 48px 40px;">
|
||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:14px;color:${clr.textSoft};line-height:1.7;">
|
||
If something stopped you from getting started, just reply. I'll help directly.
|
||
</p>
|
||
<p style="margin:16px 0 0;font-family:'DM Serif Display',Georgia,serif;
|
||
font-size:17px;color:${clr.text};">Timo</p>
|
||
<p style="margin:2px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:11px;color:${clr.textMuted};letter-spacing:0.5px;">FOUNDER, QR MASTER</p>
|
||
</td>
|
||
</tr>
|
||
`);
|
||
|
||
await transport.sendMail({
|
||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||
replyTo: 'support@qrmaster.net',
|
||
to: email,
|
||
subject: "You haven't made one yet",
|
||
html,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 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();
|
||
const pricingUrl = `${appUrl}/pricing`;
|
||
const firstName = name.split(' ')[0];
|
||
|
||
const features = [
|
||
{
|
||
label: 'Dynamic QR codes',
|
||
free: '3',
|
||
pro: '50',
|
||
},
|
||
{
|
||
label: 'Logo in QR code',
|
||
free: '—',
|
||
pro: 'Your logo, centered',
|
||
},
|
||
{
|
||
label: 'Brand colors',
|
||
free: '—',
|
||
pro: 'Any color',
|
||
},
|
||
{
|
||
label: 'CSV export',
|
||
free: '✓',
|
||
pro: '✓',
|
||
},
|
||
];
|
||
|
||
const rowsHtml = features.map((f, i) => `
|
||
<tr style="background-color:${i % 2 === 0 ? '#FAFAF8' : '#FFFFFF'};">
|
||
<td style="padding:12px 16px;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:13px;color:${clr.textSoft};border-bottom:1px solid ${clr.border};">
|
||
${f.label}
|
||
</td>
|
||
<td style="padding:12px 16px;text-align:center;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:13px;color:${clr.textMuted};border-bottom:1px solid ${clr.border};">
|
||
${f.free}
|
||
</td>
|
||
<td style="padding:12px 16px;text-align:center;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:13px;font-weight:500;color:${clr.text};border-bottom:1px solid ${clr.border};">
|
||
${f.pro}
|
||
</td>
|
||
</tr>`).join('');
|
||
|
||
const html = emailShell('', `
|
||
<!-- ── HEADER ── -->
|
||
<tr>
|
||
<td style="background-color:${clr.header};background-image:${dotGridPattern};
|
||
background-size:24px 24px;padding:44px 48px 40px;">
|
||
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||
<tr>
|
||
<td style="border:1px solid rgba(200,162,87,0.35);border-radius:8px;padding:7px 18px;">
|
||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;
|
||
font-weight:600;letter-spacing:3px;color:${clr.gold};
|
||
text-transform:uppercase;">QR Master</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<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>
|
||
<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
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── BODY ── -->
|
||
<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};">
|
||
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.
|
||
</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:
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── COMPARISON TABLE ── -->
|
||
<tr>
|
||
<td style="padding:24px 48px 0;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
||
style="border-radius:10px;overflow:hidden;border:1px solid ${clr.border};">
|
||
<!-- Table header -->
|
||
<tr>
|
||
<td style="padding:12px 16px;background-color:${clr.pillBg};
|
||
font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;
|
||
font-weight:600;letter-spacing:1px;color:${clr.textMuted};
|
||
text-transform:uppercase;border-bottom:1px solid ${clr.border};width:45%;">
|
||
Feature
|
||
</td>
|
||
<td style="padding:12px 16px;background-color:${clr.pillBg};text-align:center;
|
||
font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;
|
||
font-weight:600;letter-spacing:1px;color:${clr.textMuted};
|
||
text-transform:uppercase;border-bottom:1px solid ${clr.border};width:20%;">
|
||
Free
|
||
</td>
|
||
<td style="padding:12px 16px;background-color:${clr.header};text-align:center;
|
||
font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;
|
||
font-weight:600;letter-spacing:1px;color:${clr.gold};
|
||
text-transform:uppercase;border-bottom:1px solid ${clr.border};width:35%;">
|
||
Pro
|
||
</td>
|
||
</tr>
|
||
${rowsHtml}
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── CTA ── -->
|
||
<tr>
|
||
<td style="padding:32px 48px 44px;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td align="center">
|
||
<a href="${pricingUrl}"
|
||
style="display:inline-block;background-color:${clr.ctaBg};color:${clr.ctaText};
|
||
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);">
|
||
See Pro plan →
|
||
</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 free plan stays active — no pressure</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── DIVIDER + SIGN-OFF ── -->
|
||
<tr><td style="padding:0 48px;"><div style="border-top:1px solid ${clr.border};"></div></td></tr>
|
||
<tr>
|
||
<td style="padding:28px 48px 40px;">
|
||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:14px;color:${clr.textSoft};line-height:1.7;">
|
||
If you have questions about what Pro unlocks, just reply here.
|
||
</p>
|
||
<p style="margin:16px 0 0;font-family:'DM Serif Display',Georgia,serif;
|
||
font-size:17px;color:${clr.text};">Timo</p>
|
||
<p style="margin:2px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:11px;color:${clr.textMuted};letter-spacing:0.5px;">FOUNDER, QR MASTER</p>
|
||
</td>
|
||
</tr>
|
||
`);
|
||
|
||
await transport.sendMail({
|
||
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`,
|
||
html,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 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) {
|
||
const transport = createSmtpTransport();
|
||
const pricingUrl = `${appUrl}/pricing`;
|
||
const firstName = name.split(' ')[0];
|
||
|
||
const html = emailShell('', `
|
||
<!-- ── HEADER ── -->
|
||
<tr>
|
||
<td style="background-color:${clr.header};background-image:${dotGridPattern};
|
||
background-size:24px 24px;padding:44px 48px 40px;">
|
||
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||
<tr>
|
||
<td style="border:1px solid rgba(200,162,87,0.35);border-radius:8px;padding:7px 18px;">
|
||
<span style="font-family:'DM Sans',-apple-system,sans-serif;font-size:11px;
|
||
font-weight:600;letter-spacing:3px;color:${clr.gold};
|
||
text-transform:uppercase;">QR Master</span>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<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;">
|
||
A month in,<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 30
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── BODY ── -->
|
||
<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};">
|
||
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.
|
||
</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.
|
||
</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:
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── FEATURES ── -->
|
||
<tr>
|
||
<td style="padding:28px 48px 0;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<!-- Logo row -->
|
||
<tr>
|
||
<td style="padding:24px;background-color:${clr.pillBg};border-radius:12px;
|
||
border:1px solid ${clr.border};">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td style="width:36px;vertical-align:top;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||
<tr>
|
||
<td style="width:36px;height:36px;background-color:${clr.header};border-radius:10px;
|
||
text-align:center;vertical-align:middle;font-size:18px;color:${clr.gold};">
|
||
◆
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</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>
|
||
<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.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
<!-- Spacer -->
|
||
<tr><td style="height:16px;"></td></tr>
|
||
<!-- Colors row -->
|
||
<tr>
|
||
<td style="padding:24px;background-color:${clr.pillBg};border-radius:12px;
|
||
border:1px solid ${clr.border};">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td style="width:36px;vertical-align:top;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||
<tr>
|
||
<td style="width:36px;height:36px;background-color:${clr.header};border-radius:10px;
|
||
text-align:center;vertical-align:middle;font-size:18px;color:${clr.gold};">
|
||
●
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</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>
|
||
<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.
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── CTA ── -->
|
||
<tr>
|
||
<td style="padding:32px 48px 44px;">
|
||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||
<tr>
|
||
<td align="center">
|
||
<a href="${pricingUrl}"
|
||
style="display:inline-block;background-color:${clr.ctaBg};color:${clr.ctaText};
|
||
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 →
|
||
</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>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- ── DIVIDER + SIGN-OFF ── -->
|
||
<tr><td style="padding:0 48px;"><div style="border-top:1px solid ${clr.border};"></div></td></tr>
|
||
<tr>
|
||
<td style="padding:28px 48px 40px;">
|
||
<p style="margin:0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:14px;color:${clr.textSoft};line-height:1.7;">
|
||
If you ever want to talk through whether Pro makes sense for what you're building, just reply. Happy to help figure it out.
|
||
</p>
|
||
<p style="margin:16px 0 0;font-family:'DM Serif Display',Georgia,serif;
|
||
font-size:17px;color:${clr.text};">Timo</p>
|
||
<p style="margin:2px 0 0;font-family:'DM Sans',-apple-system,sans-serif;
|
||
font-size:11px;color:${clr.textMuted};letter-spacing:0.5px;">FOUNDER, QR MASTER</p>
|
||
</td>
|
||
</tr>
|
||
`);
|
||
|
||
await transport.sendMail({
|
||
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`,
|
||
html,
|
||
});
|
||
}
|