mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-23 23:28:04 +02:00
Boîte mail de l'association : trois transports, une file d'attente
Le site savait composer de beaux messages et ne savait pas les envoyer : `email-client.ts` ne proposait que la copie ou le téléchargement d'un brouillon Outlook, complété à la main. `/backend/boite-mail` branche désormais la boîte de l'association, sur le modèle exact de l'écran Réseaux sociaux — secrets chiffrés, jamais renvoyés au navigateur, champ laissé vide qui conserve la valeur enregistrée, contrôle de santé qui ne lève jamais. Trois transports, un seul actif à la fois, désigné par l'administrateur — pas de cascade automatique : si Google se bloque, la bascule se voit. - **Google** passe par l'API Gmail plutôt que par SMTP avec XOAUTH2 : celui-ci exigerait `https://mail.google.com/`, portée *restreinte* et donc audit de sécurité, là où `gmail.send` est simplement *sensible*. - **Microsoft** passe par Graph : l'authentification basique SMTP est désactivée depuis 2024, y compris sur outlook.com et hotmail.com. - **SMTP** couvre le reste via `nodemailer`, seule dépendance ajoutée. `from_address` est **lu chez le fournisseur** et non saisi : Gmail expédie comme l'utilisateur authentifié, Graph comme la boîte. Une adresse d'un autre domaine ferait tomber SPF et DKIM. La file `mail_messages` porte un destinataire unique par ligne — la confidentialité d'une diffusion est structurelle, aucune copie partagée n'est possible. Elle est vidée par la boucle de fond, avec réclamation en `FOR UPDATE SKIP LOCKED`, réessais espacés et reprise des verrous laissés par un conteneur arrêté en plein envoi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QsLjRAnuLwivqxCbM4WeP
This commit is contained in:
19 files changed
+3997
-16
No files matched your search
@@ -1,13 +1,19 @@
|
||||
/**
|
||||
* Boucle de libération des publications programmées. Fichier séparé de
|
||||
* `instrumentation.ts` : il touche la base et ne doit donc jamais entrer dans
|
||||
* le bundle edge du middleware.
|
||||
* Boucles de fond : libération des publications programmées et vidage de la
|
||||
* file d'envoi. Fichier séparé de `instrumentation.ts` : il touche la base et
|
||||
* `nodemailer`, et ne doit donc jamais entrer dans le bundle edge du
|
||||
* middleware — c'est cet import-là que l'`IgnorePlugin` de `next.config.mjs`
|
||||
* coupe.
|
||||
*/
|
||||
import { releaseDuePromotions } from "@/lib/promo-publish";
|
||||
import { processOutbox } from "@/lib/mail-outbox";
|
||||
|
||||
const INTERVAL_MS = 60_000;
|
||||
const PROMO_INTERVAL_MS = 60_000;
|
||||
// Plus court que la minute des promotions : un mot de passe ou une invitation
|
||||
// mis en file ne doit pas attendre une minute pleine.
|
||||
const MAIL_INTERVAL_MS = 20_000;
|
||||
|
||||
async function tick() {
|
||||
async function promoTick() {
|
||||
try {
|
||||
const n = await releaseDuePromotions();
|
||||
if (n > 0) console.log(`[promotions] ${n} publication(s) programmée(s) mise(s) en ligne`);
|
||||
@@ -18,7 +24,21 @@ async function tick() {
|
||||
}
|
||||
}
|
||||
|
||||
async function mailTick() {
|
||||
try {
|
||||
const { sent, failed } = await processOutbox();
|
||||
if (sent || failed) console.log(`[mail] ${sent} envoyé(s), ${failed} en échec`);
|
||||
} catch (error) {
|
||||
console.error("[mail] file d'attente :", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Un premier passage au démarrage rattrape les échéances tombées pendant un
|
||||
// redéploiement ou une coupure.
|
||||
void tick();
|
||||
setInterval(tick, INTERVAL_MS).unref();
|
||||
void promoTick();
|
||||
setInterval(promoTick, PROMO_INTERVAL_MS).unref();
|
||||
|
||||
if (process.env.MAIL_WORKER !== "off") {
|
||||
void mailTick();
|
||||
setInterval(mailTick, MAIL_INTERVAL_MS).unref();
|
||||
}
|
||||
Reference in new issue
Block a user