Files
PleinR/drizzle/0019_nostalgic_warstar.sql
T
Claude 8d4443a1a1 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
2026-09-14 16:00:11 +00:00

58 lines
3.1 KiB
SQL

CREATE TYPE "public"."mail_kind" AS ENUM('credentials', 'information', 'meeting', 'studio', 'test');--> statement-breakpoint
CREATE TYPE "public"."mail_provider" AS ENUM('google', 'microsoft', 'smtp');--> statement-breakpoint
CREATE TYPE "public"."mail_status" AS ENUM('queued', 'sending', 'sent', 'failed', 'cancelled');--> statement-breakpoint
CREATE TABLE "mail_accounts" (
"id" serial PRIMARY KEY NOT NULL,
"provider" "mail_provider" NOT NULL,
"from_address" varchar(200),
"from_name" varchar(200),
"is_active" boolean DEFAULT false NOT NULL,
"app_id" varchar(200),
"app_secret" text,
"access_token" text,
"refresh_token" text,
"expires_at" timestamp with time zone,
"smtp_host" varchar(200),
"smtp_port" integer,
"smtp_secure" boolean DEFAULT true NOT NULL,
"smtp_user" varchar(200),
"smtp_password" text,
"connected_by_id" integer,
"connected_at" timestamp with time zone,
"last_check_at" timestamp with time zone,
"last_check_ok" boolean,
"last_check_error" text,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "mail_accounts_provider_unique" UNIQUE("provider")
);
--> statement-breakpoint
CREATE TABLE "mail_messages" (
"id" serial PRIMARY KEY NOT NULL,
"kind" "mail_kind" NOT NULL,
"status" "mail_status" DEFAULT 'queued' NOT NULL,
"to_address" varchar(200) NOT NULL,
"to_name" varchar(200),
"subject" varchar(300) NOT NULL,
"html" text NOT NULL,
"text" text,
"reply_to" varchar(200),
"information_id" integer,
"meeting_id" integer,
"member_id" integer,
"created_by_id" integer,
"provider" "mail_provider",
"attempts" integer DEFAULT 0 NOT NULL,
"next_attempt_at" timestamp with time zone DEFAULT now() NOT NULL,
"locked_at" timestamp with time zone,
"sent_at" timestamp with time zone,
"error" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "mail_accounts" ADD CONSTRAINT "mail_accounts_connected_by_id_users_id_fk" FOREIGN KEY ("connected_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "mail_messages" ADD CONSTRAINT "mail_messages_information_id_informations_id_fk" FOREIGN KEY ("information_id") REFERENCES "public"."informations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "mail_messages" ADD CONSTRAINT "mail_messages_meeting_id_meetings_id_fk" FOREIGN KEY ("meeting_id") REFERENCES "public"."meetings"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "mail_messages" ADD CONSTRAINT "mail_messages_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "mail_messages" ADD CONSTRAINT "mail_messages_created_by_id_users_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "mail_messages_due_idx" ON "mail_messages" USING btree ("status","next_attempt_at");--> statement-breakpoint
CREATE INDEX "mail_messages_information_idx" ON "mail_messages" USING btree ("information_id");