11 KiB
Ultimail
Client mail unifié multi-comptes avec backend de synchronisation, frontend web (Next.js) et desktop (Tauri).
Vision & positionnement
Alternative souveraine à Gmail et Google Suite. L'interface s'inspire fortement de Google pour que les utilisateurs ne soient pas dépaysés et conservent leurs habitudes tout en migrant progressivement leur infrastructure vers cette solution.
Objectifs clés
- Parité fonctionnelle — supporter un maximum de fonctionnalités utiles des suites mail existantes (Gmail, Outlook)
- Au-delà de Gmail — fonctionnalités de pointe absentes ou limitées chez les géants : connectivité contrôlée avec agents IA, bots, webhooks avancés avec templates
- Expérience uniforme cross-plateforme — même UX sur desktop web, desktop app (Tauri) et mobile, avec les détails d'implémentation qui rendent cette uniformité possible
- Migration progressive — un utilisateur peut rattacher ses comptes existants et migrer à son rythme
Architecture
┌─────────────────────────────────────────────────────────┐
│ Clients (frontend) │
│ ├─ Web — Next.js 16 / React 19 (ce repo) │
│ └─ Desktop — Tauri (wrapper du même frontend) │
├─────────────────────────────────────────────────────────┤
│ Backend (futur) │
│ ├─ Client IMAP/POP3 + SMTP (collecte & envoi) │
│ ├─ Moteur de règles (tri, forward, réponses auto) │
│ ├─ Scheduler (envoi programmé, actions différées) │
│ ├─ API REST/WS pour sync clients │
│ └─ API tokens fine-grained (agents IA, webhooks) │
├─────────────────────────────────────────────────────────┤
│ Database — PostgreSQL │
│ ├─ Mails + métadonnées complètes + méta Ultimail │
│ ├─ Identifiants connexion serveurs mail │
│ ├─ Comptes Ultimail, préférences, libellés, dossiers │
│ ├─ Règles de tri, webhooks, tokens API │
│ └─ Auth comptes Ultimail │
└─────────────────────────────────────────────────────────┘
Modèle de domaine
Compte Ultimail vs Compte mail
| Concept | Description |
|---|---|
| Compte Ultimail | Compte utilisateur sur la plateforme. Possède préférences, libellés unifiés, règles, auth. |
| Compte mail | Connexion SMTP/IMAP ou POP3 vers un serveur/fournisseur. Un compte Ultimail en gère plusieurs. |
| Identité d'envoi | Adresse "From" utilisée pour envoyer. Peut différer du compte mail d'envoi (alias, catch-all). |
| Identité de réception | Adresse de destination. Peut différer du compte mail de réception (catch-all, routing). |
Relations clés
- 1 compte Ultimail → N comptes mail (envoi et/ou réception)
- 1 compte mail → N identités d'envoi / réception (catch-all, alias)
- Adresse de réception ≠ nécessairement le compte mail de réception (liées mais distinctes)
- Adresse d'envoi ≠ nécessairement le compte mail d'envoi (envoi via un autre serveur)
- Libellés et dossiers unifiés cross-comptes au niveau du compte Ultimail
Backend — Responsabilités (futur)
Synchronisation & collecte
- Client IMAP/SMTP permanent (toujours en ligne, même clients offline)
- Collecte des mails de tous les comptes mail rattachés
- Sync bidirectionnelle avec les clients (web/Tauri)
- Configuration unique propagée à tous les clients
Règles de tri & automatisations
- Règles de tri à la réception (conditions → actions)
- Envoi programmé (schedule pour le lendemain, etc.)
- Réponses automatiques
- Forward automatique
- Webhooks à la réception selon règles
Intégrations IA & programmatiques
- Tri par LLM (fournisseurs OpenAI-compatibles) avec contexte/prompt personnalisé par règle
- Tokens API fine-grained : accès partiel pour agents IA (lire certains mails, envoyer, catégoriser)
- Comportements programmatiques personnalisés par l'utilisateur
Stockage (PostgreSQL)
- Mails complets + métadonnées originales + métadonnées Ultimail (libellés, dossiers, statuts)
- Identifiants de connexion aux serveurs mail (chiffrés)
- Réglages comptes Ultimail, préférences d'organisation
- Règles de tri, webhooks, tokens API, auth
Frontend — Responsabilités (actuel)
Web (Next.js 16 — ce repo)
- Interface mail complète (liste, lecture, composition, recherche, contacts)
- Navigation URL-driven (
/mail/{folder},/mail/search,/contacts) - État persisté côté client (Zustand + localStorage) — sera remplacé par sync backend
- Mock data actuellement (
lib/email-data.ts,lib/contacts/mock-data.ts)
Desktop (Tauri — futur)
- Même frontend wrappé dans Tauri
- Accès natif (notifications, raccourcis système, stockage local)
Réglages & règles
Tous les réglages sont gérables depuis l'interface settings d'Ultimail :
Niveaux de configuration
- Global (compte Ultimail) — libellés unifiés, préférences d'affichage, densité, thème
- Par identité mail — signature, nom affiché, réponse par défaut, règles spécifiques
Types de règles
- Tri automatique (conditions sur expéditeur, sujet, contenu → libellé, dossier, archive)
- Forward automatique
- Réponse automatique
- Envoi programmé
- Webhook (POST vers URL externe à la réception)
- Tri IA (prompt + contexte personnalisé, fournisseur LLM configurable)
- Actions API (tokens fine-grained pour agents externes)
Webhooks — système de templates
Les webhooks supportent un format de payload personnalisable via templates réutilisables.
Principe : l'utilisateur définit un template (ex: "Slack", "Discord", "Custom") qui décrit le JSON à envoyer, avec des variables interpolées depuis le mail déclencheur.
Variables disponibles (exemples) :
$sender.name, $sender.email
$subject
$body.textContent, $body.htmlContent
$date, $timestamp
$recipients.to, $recipients.cc
$attachments.count, $attachments.names
$labels, $folder
$account.email (identité de réception)
Exemple — template Slack :
{
"text": "Nouveau mail de $sender.name",
"blocks": [
{
"type": "section",
"text": { "type": "mrkdwn", "text": "*$subject*\nDe: $sender.email\n$body.textContent" }
}
]
}
Cela permet de brancher n'importe quel service (Slack, Discord, n8n, Make, custom) sans code, juste en adaptant le template JSON et l'URL du webhook.
Stack technique (actuel)
| Couche | Choix |
|---|---|
| Framework | Next.js 16 (App Router, standalone) |
| UI | React 19, TypeScript 5.7 |
| Styles | Tailwind CSS 4, shadcn/ui (Radix, style new-york) |
| État | Zustand 5 (persist JSON debounced) |
| Éditeur riche | TipTap 3 |
| Icônes | @iconify/react, lucide-react |
| Recherche client | fuse.js |
| Package manager | pnpm |
| Deploy | Docker multi-stage → Node 22 Alpine, CapRover |
Conventions
Navigation
- URL = source de vérité (dossier, onglet inbox, page, message ouvert)
useMailRoute+lib/mail-url.tspour parsing/building- Recherche via query params sur
/mail/search
Stores (Zustand)
- Persistés : mail-store, mail-settings-store, nav-store, account-store, scheduled-store
- Éphémères : mail-search-store, mail-ui-store
- Ne pas persister l'UI éphémère sauf besoin produit explicite
Composants (components/gmail/)
- Organisation par feature :
compose/,email-list/,email-view/,sidebar/,mail-search/,contacts/ - Re-exports publics à la racine (
email-list.tsx→email-list/) - Hooks dédiés par domaine dans chaque feature
Docs internes
components/gmail/README.md— arborescence composantslib/stores/README.md— architecture stores
Environnement local & agents
Stack dev typique : nginx (:80) → ultid (Docker) + frontend Next.js (pnpm dev, proxy /api/v1 via ULTI_PROXY_ORIGIN).
Les agents doivent redémarrer les services eux-mêmes quand un changement l'exige — ne pas demander au développeur de le faire.
| Changement | Action (depuis ulti-backend/) |
|---|---|
Code Go (internal/, cmd/, migrations embarquées) |
./deploy/compose-up.sh up -d --build ultid |
Variable .env lue au démarrage d'ultid |
./deploy/compose-up.sh up -d --build ultid (régénère .env.resolved) |
| Module Compose optionnel (OpenWebUI, Nextcloud, …) | ./deploy/compose-up.sh up -d (overlay activé selon .env) |
| Redémarrage simple sans rebuild | ./deploy/compose-up.sh restart ultid |
| Frontend Next.js (ce repo) | pnpm dev se recharge seul ; rebuild seulement si config Next/env change |
Après restart backend, vérifier : curl -s http://127.0.0.1:80/api/v1/ai/config (JSON, pas 502).
Repo backend : ../ulti-backend — voir aussi ulti-backend/CLAUDE.md.
Mobile (Tauri 2 — Android + iOS)
Workspace : mobile/ — voir mobile/README.md (build, Phase 0, scaffolds natifs).
Toolchain Rust : utiliser rustup, pas Homebrew. Sur macOS, Rust peut coexister via brew install rust (/opt/homebrew/bin) et rustup (~/.cargo/bin). Pour Tauri mobile, toujours la toolchain rustup :
- Vérifier avant tout
cargo/pnpm tauri:which rustc cargo # attendu : ~/.cargo/bin/rustc et ~/.cargo/bin/cargo rustc --version - Si
which rustcpointe vers/opt/homebrew/bin/rustc, recharger le shell (source "$HOME/.cargo/env") ou placer~/.cargo/binavant/opt/homebrew/bindans lePATH. - Cibles mobiles (une fois) :
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
Build frontend mobile (prérequis à chaque app Tauri) : pnpm build:mobile → ./out.
Compile Rust : cd mobile && cargo check.
Commandes Tauri (layout imbriqué mobile/apps/<id>/src-tauri) :
pnpm tauri:ultimail android init # pas pnpm tauri --config depuis la racine
pnpm tauri:ultimail android dev
Les agents ne doivent pas supposer que le Rust Homebrew suffit pour les builds Android/iOS.