Email marketing V2

This commit is contained in:
2026-07-29 10:42:21 +02:00
parent 11fdec610f
commit 6fd0ed8522
9 changed files with 256 additions and 53 deletions

View File

@@ -26,11 +26,22 @@ export const authOptions: NextAuthOptions = {
where: { email: credentials.email },
});
if (!user || !user.password) {
return null;
}
const isPasswordValid = await comparePassword(
if (!user || !user.password) {
return null;
}
// Existing legacy accounts may not have this field populated. New
// password signups have a pending verification token until they
// confirm their email, and cannot sign in before doing so.
if (!user.emailVerified) {
const pendingVerification = await db.verificationToken.findFirst({
where: { identifier: user.email, expires: { gt: new Date() } },
select: { token: true },
});
if (pendingVerification) return null;
}
const isPasswordValid = await comparePassword(
credentials.password,
user.password
);
@@ -83,4 +94,4 @@ export const authOptions: NextAuthOptions = {
error: '/login',
},
secret: process.env.NEXTAUTH_SECRET,
};
};

View File

@@ -561,6 +561,20 @@ function createSmtpTransport() {
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://www.qrmaster.net';
export async function sendEmailVerificationEmail(email: string, name: string, verificationUrl: string) {
const transport = createSmtpTransport();
const firstName = name.trim().split(/\s+/)[0] || 'there';
await transport.sendMail({
from: 'Timo from QR Master <timo@qrmaster.net>',
replyTo: 'support@qrmaster.net',
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>`,
text: `Hi ${firstName},\n\nConfirm your QR Master email address: ${verificationUrl}\n\nThis link expires in 24 hours.`,
});
}
/** Marketing announcement with a per-recipient, functional unsubscribe link. */
export async function sendDesignerAnnouncementEmail(email: string, unsubscribeUrl: string) {
await waitForRateLimit();