@creezio/mails
v0.26.1
Published
Mails natifs plateforme Creezio (boîte générique + PJ + UI + slot providers marque)
Readme
@creezio/mails
Rôle
@creezio/mails fournit la capacité mails native Creezio (v2 — multi-transport) :
- outbox durable : tout envoi passe par
enqueue()(jamais bloquant), worker de drain avec retries backoff, journalmail_events, statutsqueued → sending → sent → delivered/bounced; - transports : SMTP direct (préréglage Cloudflare Email Service),
Resend (fetch natif), file-sink (dev/CI) — résolus par configuration
(réglages instance > env
MAIL_TRANSPORT> rétro-inférenceSMTP_*) ; - réception : chaîne Cloudflare Email Routing (
POST /inbound), webhooks Resend signés Svix (statuts + inbound opt-in), comptes IMAP (imapflow+mailparser, chargés dynamiquement) ; - webmail :
MailWorkspace(3 panneaux resizable, dossiers, threads, composer Tiptap, brouillons, PJ, HTML entrant en iframe sandboxée) +MailSettings(transport, test d'envoi, comptes IMAP) ; - routes Hono
/emailcomplètes +ApiMountmince pour les modules métier ; - worker Cloudflare Email Routing dans
email-worker/.
Le package ne contient aucun template métier ni domaine marque hardcodé.
Périmètre kit vs marque
Kit
- Schéma SQLite v2 :
creezio_platform_mails(dossiers, threads, statuts, retries),creezio_platform_mail_attachments,creezio_platform_mail_events,creezio_platform_mail_accounts(IMAP),creezio_platform_mail_settings. resolveMailTransport()+ transportssmtp/resend/file-sink.startMailOutboxWorker()(drain + backoff) etstartImapSyncScheduler()— démarrés par@creezio/app-runtimecôté kernel.createEmailInboxRoutes()(API complète) etcreateMailsApiMount().- Vérification Svix des webhooks Resend (
verifySvixSignature). - UI
MailWorkspace/MailSettingset worker Cloudflare générique.
Marque
- Appelle
configureMailsavecrootDomain, secrets et préférences UI. - Monte
/api/v1/email(fait parmountBrandEmailSurfaced'app-runtime). - Envoie via
getKitMailsStore().enqueue({...})depuis ses modules métier. - Déploie/configure le worker Cloudflare et les MX, ou route les webhooks
Resend vers
/api/v1/email/webhooks/resend. - Pose
nodemailer(SMTP) etimapflow/mailparser(IMAP) dans les deps server — peers optionnels du kit, la factory les scaffolde par défaut.
Installation/build
npm run build -w @creezio/mails
npm run typecheck -w @creezio/mails
npm run test:inbox -w @creezio/mailsExports :
@creezio/mails: config, store SQLite v2, transports, résolution transport, outbox worker, webhooks Resend, comptes/sync IMAP, routes, queries inbox, migration.@creezio/mails/ui:MailWorkspace,MailSettings,MailComposer…email-worker/: worker Cloudflare et bootstrap de déploiement.
Configuration du transport
Priorité de résolution (resolveMailTransport) :
- Réglages instance (
creezio_platform_mail_settings, édités par la page/parametres/email— owner only) ; - Env :
MAIL_TRANSPORT=cloudflare|smtp|resend|file-sink; - Rétro-inférence :
CLOUDFLARE_EMAIL_API_TOKEN→ cloudflare ;SMTP_URL/SMTP_HOST→ smtp ;RESEND_API_KEY→ resend ; - sinon : non configuré — les mails restent
queuedpuis passentfailed_permanentavec erreur explicite.
Variables par transport :
| Transport | Variables |
|---|---|
| cloudflare | CLOUDFLARE_EMAIL_API_TOKEN / CLOUDFLARE_EMAIL_TOKEN (ou SMTP_PASS) — token API avec la permission Email Sending: Edit, domaine onboardé sous Email Service ; host/port/user imposés (smtp.mx.cloudflare.net:465, TLS implicite, user api_token) |
| smtp | SMTP_URL ou SMTP_HOST/SMTP_PORT/SMTP_SECURE/SMTP_USER/SMTP_PASS |
| resend | RESEND_API_KEY, opt. RESEND_WEBHOOK_SECRET (Svix), MAIL_INBOUND_RESEND=1 (inbound opt-in) |
| file-sink | MAIL_FILE_SINK_DIR (défaut {data}/mails-out) |
Commun : MAIL_FROM (expéditeur par défaut). Les secrets peuvent être des
références integration://<slug> résolues via le store @creezio/integrations
(bridge injecté par app-runtime — configureMailSecretBridge).
Envoi (outbox durable)
import { getKitMailsStore } from "@creezio/mails";
const store = getKitMailsStore();
const { id } = store.enqueue({
to: ["[email protected]"],
subject: "Votre commande",
text: "Merci !",
html: "<p>Merci !</p>",
attachments: [{ filename: "facture.pdf", contentType: "application/pdf", content: buf }],
});
// Non bloquant : le worker outbox draine, retries backoff, journal mail_events.Cycle de vie : queued → sending → sent → (delivered | bounced
via webhook Resend). Échec réessayable → backoff exponentiel
(1 min → 5 min → 15 min → 1 h → 6 h, max 8 tentatives) ; échec permanent →
failed_permanent + événement.
Worker : startMailOutboxWorker({ store, resolve }) — démarré par
app-runtime côté kernel uniquement (CREEZIO_MAIL_OUTBOX=0 pour couper).
Réception
- Cloudflare Email Routing (inchangé) : worker →
POST /inboundavec secret partagé (EMAIL_INBOUND_SECRET). - Webhooks Resend :
POST /webhooks/resendsigné Svix (RESEND_WEBHOOK_SECRET) — statuts delivered/bounced/complained +email.received(inbound opt-inMAIL_INBOUND_RESEND=1). - IMAP : comptes CRUD via
/accounts(owner), sync incrémentale par UID (startImapSyncScheduler,CREEZIO_MAIL_IMAP=0pour couper). Secrets stockés en clair jamais — référenceintegration://ou champ chiffré store.
API /email (montée sous /api/v1/email)
- Public (secret/signature) :
POST /inbound,POST /webhooks/resend. - Session :
GET /meta,GET /(dossiersinbox|sent|drafts|outbox|archive|trash),GET /:id,GET /:id/attachments/:attId,GET /:id/events,GET /threads/:threadId,PATCH /:id(lu / dossier),DELETE /:id,POST /send,POST /drafts,PUT /drafts/:id,POST /drafts/:id/send,POST /attachments. - Owner :
GET|PUT /settings,POST /settings/verify(ok: truesi identifiants présents même si le send est KO — voirsend.state).GET|POST /accounts,PATCH|DELETE /accounts/:id,POST /accounts/:id/verify,POST /accounts/:id/sync.
UI
import { MailWorkspace, MailSettings } from "@creezio/mails/ui";
// /mails
<MailWorkspace />
// /parametres/email (owner)
<MailSettings />Sécurité rendu : le HTML entrant est rendu dans une iframe sandboxée
(sandbox="", sans allow-scripts ni allow-same-origin) — jamais
d'injection directe dans le DOM de l'app.
Peers UI optionnels : @tiptap/react, @tiptap/starter-kit,
@tiptap/extension-link (fallback textarea si absents),
react-resizable-panels + @radix-ui/react-tooltip via @creezio/shell-ui.
Dépendances
- Runtime :
@creezio/api-kernel,@creezio/auth,@creezio/platform-core,@creezio/shell-ui,hono. - Peers optionnels (chargés dynamiquement) :
nodemailer(smtp),imapflow+mailparser(IMAP), Tiptap (UI). Resend = fetch natif, zéro dépendance. - Worker : Cloudflare Workers / Email Routing,
wranglercôté déploiement.
Gates
node --test scripts/test-mails-inbox.mjs # schéma v2 + threads + migration v1
node --test scripts/test-phase-mails-transports.mjs # résolution + SMTP local + mock Resend
node --test scripts/test-phase-mails-outbox.mjs # enqueue/retries/PJ/brouillons
node --test scripts/test-phase-mails-webhooks.mjs # Svix + statuts + inbound opt-in
node --test scripts/test-phase-mails-imap.mjs # comptes + sync mock IMAP
node --test scripts/test-phase-mails-ui.mjs # webmail + iframe sandbox + wrappers