191 lines
No EOL
5.7 KiB
TypeScript
191 lines
No EOL
5.7 KiB
TypeScript
// app/api/staff/bulk-email/route.ts
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { createSbServer } from "@/lib/supabaseServer";
|
|
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
|
|
|
|
interface Recipient {
|
|
id: string;
|
|
email: string;
|
|
organizations: Array<{
|
|
id: string;
|
|
name: string;
|
|
role: string;
|
|
structure_api: string | null;
|
|
}>;
|
|
}
|
|
|
|
interface BulkEmailRequest {
|
|
subject: string;
|
|
htmlContent: string;
|
|
recipients: Recipient[];
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const sb = createSbServer();
|
|
|
|
// Vérifier que l'utilisateur est authentifié et a le rôle staff
|
|
const { data: { user }, error: authError } = await sb.auth.getUser();
|
|
|
|
if (authError || !user) {
|
|
return NextResponse.json(
|
|
{ error: "Non authentifié" },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
// Vérifier les permissions staff
|
|
const { data: staffMember, error: staffError } = await sb
|
|
.from("staff_users")
|
|
.select("is_staff")
|
|
.eq("user_id", user.id)
|
|
.maybeSingle();
|
|
|
|
if (staffError || !staffMember?.is_staff) {
|
|
return NextResponse.json(
|
|
{ error: "Accès refusé - permissions insuffisantes" },
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
|
|
const body: BulkEmailRequest = await request.json();
|
|
const { subject, htmlContent, recipients } = body;
|
|
|
|
// Validation des données
|
|
if (!subject?.trim() || !htmlContent?.trim() || !recipients?.length) {
|
|
return NextResponse.json(
|
|
{ error: "Tous les champs sont requis" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (recipients.length > 100) {
|
|
return NextResponse.json(
|
|
{ error: "Maximum 100 destinataires par envoi" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Configuration SES
|
|
const region = process.env.AWS_REGION || "eu-west-3";
|
|
const fromEmail = process.env.AWS_SES_FROM;
|
|
|
|
if (!fromEmail) {
|
|
return NextResponse.json(
|
|
{ error: "Configuration SES manquante" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
const ses = new SESClient({ region });
|
|
|
|
// Créer le contenu texte simple à partir du HTML
|
|
const textContent = htmlContent
|
|
.replace(/<[^>]*>/g, '') // Supprimer les balises HTML
|
|
.replace(/\s+/g, ' ') // Normaliser les espaces
|
|
.trim();
|
|
|
|
let sentCount = 0;
|
|
const failedEmails: string[] = [];
|
|
|
|
// Configuration optimisée pour 14 emails/seconde
|
|
// Envoyer par lots de 12 emails avec pause de 1 seconde
|
|
const batchSize = 12;
|
|
|
|
for (let i = 0; i < recipients.length; i += batchSize) {
|
|
const batch = recipients.slice(i, i + batchSize);
|
|
|
|
const promises = batch.map(async (recipient) => {
|
|
try {
|
|
// Personnaliser le contenu si nécessaire
|
|
let personalizedHtml = htmlContent;
|
|
let personalizedSubject = subject;
|
|
|
|
// Vous pouvez ajouter des remplacements personnalisés ici
|
|
// Par exemple : personalizedHtml = personalizedHtml.replace('{{name}}', recipient.name);
|
|
|
|
// Formater l'adresse email source correctement
|
|
let sourceEmail = fromEmail;
|
|
if (fromEmail && !fromEmail.includes('<')) {
|
|
// Si fromEmail ne contient pas déjà un format "Nom <email>", on l'ajoute
|
|
sourceEmail = `Espace Paie Odentas <${fromEmail}>`;
|
|
} else {
|
|
// Si fromEmail contient déjà un format, on l'utilise tel quel
|
|
sourceEmail = fromEmail;
|
|
}
|
|
|
|
const cmd = new SendEmailCommand({
|
|
Destination: {
|
|
ToAddresses: [recipient.email]
|
|
},
|
|
Source: sourceEmail,
|
|
Message: {
|
|
Subject: {
|
|
Data: personalizedSubject,
|
|
Charset: "UTF-8"
|
|
},
|
|
Body: {
|
|
Html: {
|
|
Data: personalizedHtml,
|
|
Charset: "UTF-8"
|
|
},
|
|
Text: {
|
|
Data: textContent,
|
|
Charset: "UTF-8"
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
await ses.send(cmd);
|
|
sentCount++;
|
|
|
|
console.log(`Email envoyé avec succès à ${recipient.email}`);
|
|
} catch (error) {
|
|
console.error(`Erreur envoi email à ${recipient.email}:`, error);
|
|
failedEmails.push(recipient.email);
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
|
|
// Pause entre les lots pour respecter les limites SES
|
|
if (i + batchSize < recipients.length) {
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
}
|
|
|
|
// Log de l'activité pour traçabilité
|
|
try {
|
|
await sb
|
|
.from("email_logs") // Créez cette table si elle n'existe pas
|
|
.insert({
|
|
user_id: user.id,
|
|
type: "bulk_email",
|
|
subject: subject,
|
|
recipients_count: recipients.length,
|
|
sent_count: sentCount,
|
|
failed_count: failedEmails.length,
|
|
failed_emails: failedEmails.length > 0 ? failedEmails : null,
|
|
created_at: new Date().toISOString()
|
|
});
|
|
} catch (logError) {
|
|
console.error("Erreur lors de l'enregistrement du log:", logError);
|
|
// Ne pas faire échouer la requête pour une erreur de log
|
|
}
|
|
|
|
return NextResponse.json({
|
|
sent: sentCount,
|
|
failed: failedEmails.length,
|
|
failedEmails: failedEmails.length > 0 ? failedEmails : undefined,
|
|
message: `${sentCount} email(s) envoyé(s) avec succès${failedEmails.length > 0 ? `, ${failedEmails.length} échec(s)` : ''}`
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Erreur dans l'API bulk-email:", error);
|
|
return NextResponse.json(
|
|
{ error: "Erreur interne du serveur" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
} |