mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-11 17:27:54 +02:00
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
58 lines
3.1 KiB
SQL
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"); |