espace-paie-odentas/app/api/staff/bulk-email/route.ts
2025-10-12 17:05:46 +02:00

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 }
);
}
}