3 Commits
test ... master

Author SHA1 Message Date
5541c0553c refactor: derive email sender address dynamically from SMTP_USER 2026-08-13 19:33:05 +02:00
172730cf0f SMTP_USER 2026-08-13 18:30:34 +02:00
9ccce7cbfd info instead of timo 2026-08-13 17:44:55 +02:00
3 changed files with 39 additions and 23 deletions

View File

@@ -49,6 +49,8 @@ ARG NEXT_PUBLIC_UMAMI_SRC=""
ARG NEXT_PUBLIC_UMAMI_ID=""
ENV NEXT_PUBLIC_UMAMI_SRC=$NEXT_PUBLIC_UMAMI_SRC
ENV NEXT_PUBLIC_UMAMI_ID=$NEXT_PUBLIC_UMAMI_ID
ARG SMTP_USER=""
ENV SMTP_USER=$SMTP_USER
# Shared session cookie across www.* and app.*. Needed at build time too: process.env is
# inlined into the Edge middleware bundle, so a runtime-only value would leave the
# middleware and the route handlers disagreeing about the cookie scope.

View File

@@ -85,7 +85,7 @@ services:
RESEND_API_KEY: ${RESEND_API_KEY:-}
SMTP_HOST: ${SMTP_HOST:-smtp.qrmaster.net}
SMTP_PORT: ${SMTP_PORT:-465}
SMTP_USER: ${SMTP_USER:-timo@qrmaster.net}
SMTP_USER: ${SMTP_USER:-info@qrmaster.net}
SMTP_PASS: ${SMTP_PASS:-}
NEWSLETTER_ADMIN_EMAIL: ${NEWSLETTER_ADMIN_EMAIL:-}
NEWSLETTER_ADMIN_PASSWORD: ${NEWSLETTER_ADMIN_PASSWORD:-}

View File

@@ -48,6 +48,20 @@ async function waitForRateLimit() {
lastEmailSent = Date.now();
}
function getEmailFrom(name = 'Timo from QR Master'): string {
const address = process.env.SMTP_USER || 'timo@qrmaster.net';
return `${name} <${address}>`;
}
function getEmailFromSecurity(): string {
const address = process.env.SMTP_USER || 'noreply@qrmaster.net';
return `QR Master Security <${address}>`;
}
function getEmailReplyTo(): string {
return process.env.SMTP_USER || 'support@qrmaster.net';
}
/**
* Password Reset Email - Security focused with clear urgency
*/
@@ -58,8 +72,8 @@ export async function sendPasswordResetEmail(email: string, resetToken: string)
try {
await resend.emails.send({
from: 'QR Master Security <noreply@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFromSecurity(),
replyTo: getEmailReplyTo(),
to: email,
subject: '🔐 Reset Your QR Master Password (Expires in 1 Hour)',
html: `
@@ -190,8 +204,8 @@ export async function sendNewsletterWelcomeEmail(email: string) {
try {
await resend.emails.send({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: '🎉 You\'re In! Here\'s What Happens Next (AI QR Features)',
html: `
@@ -362,8 +376,8 @@ export async function sendAIFeatureLaunchEmail(email: string) {
try {
await resend.emails.send({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: '🚀 They\'re Live! Your AI QR Features Are Ready',
html: `
@@ -568,8 +582,8 @@ export async function sendEmailVerificationEmail(email: string, name: string, ve
const firstName = name.trim().split(/\s+/)[0] || 'there';
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: 'Confirm your QR Master email address',
html: `<!doctype html><html><body style="margin:0;background:#f5f4ef;color:#1b1c19;font-family:Arial,sans-serif;"><table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td align="center" style="padding:32px 12px;"><table role="presentation" width="600" cellspacing="0" cellpadding="0" style="width:100%;max-width:600px;background:#fff;"><tr><td style="padding:20px 32px;border-bottom:1px solid #e3e3de;font-size:11px;font-weight:bold;letter-spacing:2px;">QR MASTER</td></tr><tr><td style="padding:36px 32px;"><h1 style="margin:0 0 18px;font-family:Georgia,serif;font-size:30px;font-weight:normal;line-height:1.2;">Confirm your email address</h1><p style="margin:0;font-size:16px;line-height:1.65;">Hi ${escapeHtml(firstName)},</p><p style="font-size:16px;line-height:1.65;">Click the button below to finish creating your QR Master account.</p><a href="${verificationUrl}" style="display:inline-block;margin:10px 0 22px;background:#0047ff;color:#fff;padding:14px 22px;text-decoration:none;font-size:14px;font-weight:bold;">CONFIRM EMAIL</a><p style="margin:0;color:#747878;font-size:13px;line-height:1.6;">This link expires in 24 hours. If you did not create an account, you can ignore this email.</p></td></tr></table></td></tr></table></body></html>`,
@@ -586,8 +600,8 @@ export async function sendDesignerAnnouncementEmail(email: string, unsubscribeUr
const transport = createSmtpTransport();
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: 'Your QR codes can now look like your brand',
html: `
@@ -652,8 +666,8 @@ export async function sendNewsletterEmail({
const transport = createSmtpTransport();
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject,
html: `<!doctype html><html><body style="margin:0;background:#f5f4ef;color:#1b1c19;font-family:Arial,sans-serif;"><table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td align="center" style="padding:32px 12px;"><table role="presentation" width="600" cellspacing="0" cellpadding="0" style="width:100%;max-width:600px;background:#fff;"><tr><td style="padding:20px 32px;border-bottom:1px solid #e3e3de;font-size:11px;font-weight:bold;letter-spacing:2px;">QR MASTER</td></tr><tr><td style="padding:36px 32px;">${body}</td></tr><tr><td style="padding:20px 32px;border-top:1px solid #e3e3de;color:#747878;font-size:11px;line-height:1.6;">You are receiving this email from QR Master.<br><a href="${unsubscribeUrl}" style="color:#747878;">Unsubscribe from product updates</a></td></tr></table></td></tr></table></body></html>`,
@@ -929,8 +943,8 @@ export async function sendWelcomeEmail(email: string, name: string) {
`);
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: 'Your QR Master account is ready',
html,
@@ -1066,8 +1080,8 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
`);
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: "Your 3 free codes are still sitting there",
html,
@@ -1237,8 +1251,8 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
`);
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: 'You just hit the free limit',
html,
@@ -1413,8 +1427,8 @@ export async function sendThirtyDayNudgeEmail(
`);
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: `${firstName}, your codes were scanned ${scanCount} time${scanCount !== 1 ? 's' : ''} this month`,
html,
@@ -1528,8 +1542,8 @@ export async function sendFirstScanEmail(
`);
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
from: getEmailFrom(),
replyTo: getEmailReplyTo(),
to: email,
subject: 'Your QR code was just scanned for the first time',
html,