GET /api/health
Vérifie l'état de l'application.
Réponse :
{
"status": "ok",
"service": "MailPilot"
}GET /api/emails
Récupère la liste des emails.
Paramètres de requête :
limit(int, optionnel) : Nombre maximum d'emails à retourner (défaut: 50)folder(string, optionnel) : Dossier logique à lister. Valeurs possibles :inbox(défaut)sentspamtrash
Réponse :
{
"emails": [
{
"id": 1,
"message_id": "<message-id@example.com>",
"subject": "Demande de devis",
"sender_email": "client@example.com",
"sender_name": "Jean Dupont",
"received_date": "2025-01-28T10:30:00",
"is_replied": true,
"is_processed": true,
"is_read": false,
"classification": {
"sender_type": "human",
"is_commercial": false,
"company_name": "Example",
"company_domain": "example.com",
"confidence": 75
}
}
]
}Note : classification est null si la classification n'a pas encore été effectuée. La classification est basée sur le premier email de la conversation.
GET /api/emails/<email_id>
Récupère les détails d'un email spécifique.
Réponse :
{
"id": 1,
"message_id": "<message-id@example.com>",
"subject": "Demande de devis",
"sender_email": "client@example.com",
"sender_name": "Jean Dupont",
"body_text": "Bonjour, je souhaite...",
"body_html": "<p>Bonjour, je souhaite...</p>",
"received_date": "2025-01-28T10:30:00",
"is_replied": true,
"is_read": false,
"osint": {
"status": "done",
"data": {
"email": "client@example.com",
"name": "Jean Dupont",
"sources": ["sherlock", "maigret"],
"social_profiles": {
"Twitter": "https://twitter.com/jeandupont",
"LinkedIn": "https://linkedin.com/in/jeandupont"
},
"breach_data": {}
},
"updated_date": "2025-01-28T10:32:00"
},
"classification": {
"sender_type": "human",
"is_commercial": false,
"company_name": "Example",
"company_domain": "example.com",
"confidence": 75
},
"response": {
"id": 1,
"subject": "Re: Demande de devis",
"body": "Merci pour votre message...",
"is_sent": true,
"sent_date": "2025-01-28T10:35:00",
"tracking_token": "xxxxxxxx",
"tracking": {
"opens": 2,
"clicks": 1
}
}
}Note :
classificationvient de la conversation (basée sur le premier email)osintvient de l'identité (Identity) et est réutilisable entre emails- Si
sender_type == "robot",osintsera généralementnull(OSINT non lancé) - Les dates créées par l'application (ex.
sent_date,received_datepour les réponses envoyées depuis l'UI) sont en fuseau Europe/Paris (naive) pour un affichage cohérent côté client
DELETE /api/emails/<email_id>
Supprime un email : tentative de suppression côté IMAP puis suppression en base. Si la suppression IMAP échoue (connexion, auth, message introuvable), l'email est quand même supprimé en base pour ne pas bloquer l'UI.
Réponse 200 :
{
"success": true,
"deleted_on_imap": true
}deleted_on_imap:truesi la suppression sur le serveur IMAP a réussi,falsesinon (email supprimé uniquement en base).
Réponse 404 : {"error": "Email not found"}
Les réponses envoyées depuis l'interface et les nouveaux messages envoyés peuvent inclure un pixel invisible et des liens trackés. Lorsque le destinataire ouvre l'email ou clique sur un lien, un événement est enregistré.
GET /api/emails/<email_id>/tracking
Statistiques de tracking pour la réponse envoyée à cet email (ouvertures, clics, temps de lecture).
Réponse 200 :
{
"email_id": 1,
"response_id": 5,
"total_opens": 2,
"total_clicks": 1,
"avg_read_time": null,
"first_open": "2026-01-31T14:00:00",
"last_open": "2026-01-31T15:30:00",
"events_by_type": { "open": { "count": 2, "first_at": "...", "last_at": "..." }, "click": { "count": 1, ... } }
}GET /api/tracking/response/<response_id>
Statistiques de tracking pour une réponse envoyée (même structure, identifiée par response_id).
Routes publiques (sans auth, appelées par le client du destinataire) :
- GET
/track/pixel/<tracking_token>: pixel 1x1 pour enregistrer une ouverture. Retourne une image PNG transparente. - GET
/track/click/<tracking_token>?url=<encoded_url>: enregistre un clic puis redirige vers l'URL. - GET
/track/read-time/<tracking_token>?seconds=<n>: enregistre un temps de lecture (optionnel).
La config app.tracking_base_url doit pointer vers l'URL publique de l'application (ex. https://mailpilot.danielcraft.fr) pour que les liens de tracking dans les emails fonctionnent.
GET /api/stats
Retourne un agrégat de statistiques pour alimenter la page « Statistiques » de l'interface.
Réponse 200 :
{
"totals": {
"received": 120,
"sent": 45,
"unreplied": 8,
"conversations": 30,
"conversations_active": 12,
"pending": 3
},
"tracking": {
"tracked_total": 20,
"opened_count": 15,
"clicked_count": 5,
"open_rate": 75.0,
"click_rate": 33.3
},
"activity_7days": [
{ "date": "27/01", "received": 3, "sent": 1 },
{ "date": "28/01", "received": 5, "sent": 2 }
],
"top_senders": [
{ "email": "client@example.com", "name": "Client", "count": 10 }
],
"distribution": {
"human": 40,
"robot": 15,
"commercial": 12
}
}GET /api/conversations
Récupère la liste des conversations.
Paramètres de requête :
limit(int, optionnel) : Nombre maximum de conversations (défaut: 20)
Réponse :
{
"conversations": [
{
"id": 1,
"thread_id": "<thread-id@example.com>",
"subject": "Demande de devis",
"sender_email": "client@example.com",
"sender_name": "Jean Dupont",
"email_count": 3,
"reply_count": 2,
"last_email_date": "2025-01-28T10:30:00",
"updated_date": "2025-01-28T10:30:00"
}
]
}GET /api/contacts
Liste les contacts avec recherche et pagination.
Paramètres de requête :
q(string, optionnel) : Recherche sur email, nom, entreprise, noteslimit(int, optionnel) : Nombre max de contacts (défaut : 50)offset(int, optionnel) : Décalage pour pagination (défaut : 0)
Réponse :
{
"contacts": [
{
"id": 1,
"email": "client@example.com",
"display_name": "Jean Dupont",
"first_name": "Jean",
"last_name": "Dupont",
"company": "Example SAS",
"phone": null,
"notes": null,
"data": {
"classification": { "sender_type": "human", "is_commercial": false, "company_name": "Example" },
"osint": { "sources": ["sherlock"], "profiles": {}, "leaks": [] }
},
"created_at": "2025-01-28T10:00:00",
"updated_at": "2025-01-28T10:30:00"
}
],
"total": 42
}POST /api/contacts
Crée un nouveau contact.
Body JSON :
email(string, requis)display_name,first_name,last_name,company,phone,notes(optionnels)data(object, optionnel) : Données OSINT/classification
Réponse : 201 avec l'objet contact créé.
GET /api/contacts/<contact_id>
Récupère un contact par ID.
PATCH /api/contacts/<contact_id>
Met à jour un contact (champs fournis uniquement). Le champ data est fusionné avec l'existant.
DELETE /api/contacts/<contact_id>
Supprime un contact.
GET /api/contacts/export/vcf
Exporte tous les contacts en fichier vCard 3.0 (CardDAV). Réponse : fichier .vcf en téléchargement.
POST /api/emails/send
Envoie un email depuis l'interface (modale « Nouveau message »). Crée ou met à jour les contacts pour tous les destinataires (À, Cc, Bcc) et injecte le tracking (pixel + liens trackés) comme pour une réponse.
Body JSON :
to(string, requis) : Adresse(s) destinataire(s), séparées par des virgulescc(string, optionnel)bcc(string, optionnel)subject(string, requis)body(string, requis)
Réponse succès :
{
"success": true,
"sent_email_id": 42,
"sent_conversation_id": 10
}Réponse erreur : 400 ou 500 avec message.
L'email envoyé apparaît immédiatement dans le dossier Envoyés (folder=sent) côté UI, sans attendre une éventuelle copie automatique par le serveur SMTP dans le dossier IMAP "Sent".
POST /api/emails/<email_id>/reply
Envoie une réponse à un email depuis l'interface. Crée un message dans le fil (folder=sent) pour l'historique. Côté UI : la boîte de réponse se ferme pendant l'envoi ; en cas d'erreur, elle se rouvre avec le texte non envoyé pour permettre une nouvelle tentative.
Body JSON :
body(string) : Corps du messagesubject(string, optionnel) : Sujet (défaut : Re: sujet de l'email)
Réponse succès : { "success": true }
GET /api/conversations/<conversation_id>
Récupère l'historique complet d'une conversation.
Réponse :
{
"conversation_id": 1,
"history": [
{
"id": 1,
"message_id": "<message-id@example.com>",
"subject": "Demande de devis",
"sender_email": "client@example.com",
"sender_name": "Jean Dupont",
"body_text": "Bonjour...",
"received_date": "2025-01-28T10:00:00",
"is_replied": true,
"response": {
"body": "Merci pour votre message...",
"is_sent": true,
"sent_date": "2025-01-28T10:05:00"
}
}
]
}Événement : connect
Émis automatiquement lors de la connexion.
Réponse :
{
"status": "ok"
}Événement : email_received
Émis lorsqu'un nouvel email est détecté via IMAP IDLE ou lorsqu'un message existant est importé/synchronisé (INBOX, Envoyés, Spam, Corbeille).
Données :
{
"id": 12,
"message_id": "<message-id@example.com>",
"subject": "Demande de devis",
"conversation_id": 3,
"sender_email": "client@example.com",
"sender_name": "Jean Dupont",
"received_date": "2025-01-28T10:30:00",
"is_read": false,
"is_replied": false,
"is_processed": false,
"folder": "inbox"
}Événement : email_processing
Émis pendant le traitement d'un email.
Données :
{
"task_id": "abc123",
"step": "generation",
"progress": 70
}Étapes possibles :
initialization: Initialisationosint: Enrichissement OSINTdanielcraft_context: Récupération contexte danielcraft.frgeneration: Génération de la réponsetemplate: Application du templatesending: Envoi de la réponsecomplete: Terminé
Événement : email_error
Émis en cas d'erreur lors du traitement.
Données :
{
"task_id": "abc123",
"error": "Erreur de génération"
}Événement client : monitor_task
Démarre le monitoring d'une tâche Celery.
Données envoyées :
{
"task_id": "abc123"
}Réponses :
task_progress: Progression de la tâchetask_complete: Tâche terminéetask_error: Erreur de tâche
Événement client : get_conversations
Récupère les conversations en temps réel.
Réponse :
{
"conversations": [
{
"id": 1,
"thread_id": "<thread-id@example.com>",
"subject": "Demande de devis",
"sender_email": "client@example.com",
"email_count": 3,
"reply_count": 2
}
]
}const socket = io();
socket.on('connect', () => {
console.log('Connecté');
});
socket.on('email_received', (data) => {
console.log('Nouvel email:', data);
});
socket.on('email_processing', (data) => {
console.log(`Traitement: ${data.step} - ${data.progress}%`);
});
socket.emit('monitor_task', { task_id: 'abc123' });import requests
# Récupérer les emails
response = requests.get('http://localhost:5000/api/emails?limit=10')
emails = response.json()['emails']
# Récupérer une conversation
response = requests.get('http://localhost:5000/api/conversations/1')
conversation = response.json()POST /api/osint/email
Lance une analyse OSINT sur une adresse email (page « OSINT email »).
Body JSON :
email(string, requis) : Adresse à analyser
Réponse : Résultat texte de l'analyse (outils OSINT côté serveur / WSL).
200: Succès201: Créé (ex. contact)400: Requête invalide404: Ressource non trouvée500: Erreur serveur
Actuellement, aucun rate limiting n'est implémenté. À ajouter en production si nécessaire.