Email marketing V3

This commit is contained in:
2026-07-29 10:52:29 +02:00
parent 6fd0ed8522
commit e6fc428b15
2 changed files with 39 additions and 21 deletions

View File

@@ -13,9 +13,31 @@ import {
getEmailDomain,
parseAttributionCookie,
} from '@/lib/revops';
import { triggerLifecycleScoring } from '@/lib/revops-server';
export async function POST(request: NextRequest) {
import { triggerLifecycleScoring } from '@/lib/revops-server';
async function issueVerificationEmail(user: { email: string; name: string | null }) {
const verificationToken = crypto.randomBytes(32).toString('base64url');
const verificationUrl = new URL('/api/auth/verify-email', process.env.NEXT_PUBLIC_APP_URL || 'https://www.qrmaster.net');
verificationUrl.searchParams.set('token', verificationToken);
await db.verificationToken.deleteMany({ where: { identifier: user.email } });
await db.verificationToken.create({
data: {
identifier: user.email,
token: verificationToken,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
},
});
try {
await sendEmailVerificationEmail(user.email, user.name ?? 'there', verificationUrl.toString());
} catch (error) {
await db.verificationToken.deleteMany({ where: { token: verificationToken } });
throw error;
}
}
export async function POST(request: NextRequest) {
try {
// CSRF Protection
const csrfCheck = csrfProtection(request);
@@ -62,8 +84,18 @@ export async function POST(request: NextRequest) {
where: { email },
});
if (existingUser) {
return NextResponse.json(
if (existingUser) {
if (!existingUser.emailVerified && existingUser.password && await bcrypt.compare(password, existingUser.password)) {
try {
await issueVerificationEmail(existingUser);
return NextResponse.json({ success: true, requiresEmailVerification: true, email: existingUser.email });
} catch (emailError) {
console.error('Email verification resend failed:', emailError);
return NextResponse.json({ error: 'We could not send the confirmation email. Please try again.' }, { status: 503 });
}
}
return NextResponse.json(
{ error: 'User already exists' },
{ status: 400 }
);
@@ -96,25 +128,11 @@ export async function POST(request: NextRequest) {
triggerLifecycleScoring(user.id, 'signup');
const verificationToken = crypto.randomBytes(32).toString('base64url');
const verificationUrl = new URL('/api/auth/verify-email', process.env.NEXT_PUBLIC_APP_URL || 'https://www.qrmaster.net');
verificationUrl.searchParams.set('token', verificationToken);
await db.verificationToken.deleteMany({ where: { identifier: user.email } });
await db.verificationToken.create({
data: {
identifier: user.email,
token: verificationToken,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
},
});
// A confirmed address is required before the account can be used or receive campaigns.
try {
await sendEmailVerificationEmail(user.email, user.name ?? 'there', verificationUrl.toString());
await issueVerificationEmail(user);
} catch (emailError) {
console.error('Email verification message failed:', emailError);
await db.verificationToken.deleteMany({ where: { token: verificationToken } });
await db.user.delete({ where: { id: user.id } });
return NextResponse.json({ error: 'We could not send the confirmation email. Please try again.' }, { status: 503 });
}

View File

@@ -35,7 +35,7 @@ export const authOptions: NextAuthOptions = {
// 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() } },
where: { identifier: user.email },
select: { token: true },
});
if (pendingVerification) return null;