Documentation de l'API
URL de base : https://flamboyant-bouman.82-26-80-185.plesk.page/api/v1. Toutes les requêtes et réponses sont en JSON (UTF-8).
Authentification
Chaque projet possède sa propre clé API, visible une seule fois à sa création ou à sa régénération depuis le tableau de bord. Envoyez-la dans l'en-tête :
Authorization: Bearer pm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Origin) sont refusées avec 403 browser_request_forbidden. Si une clé est compromise, régénérez-la : l'ancienne est immédiatement révoquée.Un projet ne peut envoyer que lorsque son adresse expéditrice est validée (SPF, DKIM et DMARC) et qu'il n'est pas suspendu.
Formats et statuts
Un envoi accepté est placé en file d'attente puis expédié progressivement. Le statut d'un message est l'un des suivants :
| Statut | Signification |
|---|---|
accepted | Demande acceptée et mise en file d'attente. |
pending | En cours d'envoi ou en attente d'une nouvelle tentative. |
sent | Remis au serveur de messagerie du destinataire. |
error | Échec définitif (adresse inexistante, rejet, projet suspendu…). |
Une réponse 202 signifie que la demande est acceptée, pas encore envoyée. Consultez GET /messages/:id pour suivre l'envoi.
GET /project
Informations sur le projet associé à la clé, son état et sa consommation du jour.
{
"id": 12, "name": "Mon site", "type": "contact", "status": "active",
"sender": { "email": "contact@monsite.fr", "verified": true },
"quotas": { "daily_limit": 500, "per_minute_limit": 30, "used_today": 42 }
}
POST /send
Envoi pour les projets de type contact, notification et newsletter.
| Champ | Type | Description |
|---|---|---|
to | chaîne ou tableau | Destinataire. Jusqu'à 50 adresses pour notification et newsletter ; une seule pour contact. Chaque destinataire reçoit un message séparé. |
subject | chaîne | Sujet (200 caractères maximum). Facultatif si un modèle est utilisé. |
html | chaîne | Contenu HTML (balises autorisées ci-dessous). |
text | chaîne | Version texte brut. Générée automatiquement à partir du HTML si absente. |
template | chaîne | Nom d'un modèle enregistré, à la place de subject/html/text. |
variables | objet | Valeurs pour les balises {{nom}} du contenu ou du modèle (échappées en HTML). |
reply_to | chaîne | Adresse de réponse (utile pour les formulaires de contact). |
logo_url | chaîne | URL https d'un logo, affiché en haut de l'e-mail. Par défaut : logo du projet. |
attachments | tableau | Voir Pièces jointes. |
POST /api/v1/send
{
"to": "client@exemple.fr",
"template": "commande-expediee",
"variables": { "prenom": "Léa", "numero": "A-1042" }
}
202 Accepted
{
"status": "accepted",
"messages": [{ "id": "6f1c…", "to": "client@exemple.fr", "status": "accepted", "duplicate": false }],
"skipped": []
}
skipped liste les destinataires ignorés : désinscrits (notification, newsletter), rebonds permanents ou plaintes.
HTML autorisé : titres, paragraphes, listes, liens https/mailto, images https, tableaux et styles de présentation (couleurs, polices, marges, alignement). Les scripts, formulaires, iframes et styles de positionnement ou de masquage sont retirés.
Pied de page : chaque e-mail se termine par une mention « Propulsé par PropulseMail » et un lien de signalement, ajoutés automatiquement après votre contenu. Les notifications et newsletters incluent aussi un lien de désinscription et l'en-tête List-Unsubscribe. Ces éléments ne peuvent pas être retirés.
POST /verification/send
Pour les projets de type vérification d'e-mail. PropulseMail génère un code à 6 chiffres, valable 15 minutes, et l'insère à la place de la balise {code} de votre modèle. Le code n'est jamais renvoyé dans la réponse.
POST /api/v1/verification/send
{ "to": "nouvel.inscrit@exemple.fr", "template": "verification", "variables": { "site": "Mon site" } }
202 Accepted
{
"status": "accepted",
"verification_id": "b7e2…",
"message_id": "41aa…",
"expires_at": "2026-10-24T10:15:00.000Z"
}
Le modèle verification est créé avec le projet ; personnalisez-le depuis le tableau de bord ou via PUT /templates/verification. Il doit contenir {code}. Un nouveau code ne peut être demandé pour la même adresse qu'après 60 secondes. Les variables ne peuvent pas injecter de code.
POST /verification/check
Vérifie le code saisi par l'utilisateur. Un code est à usage unique ; 5 essais maximum.
POST /api/v1/verification/check
{ "verification_id": "b7e2…", "code": "482913" }
200 OK
{ "valid": true, "email": "nouvel.inscrit@exemple.fr" }
200 OK
{ "valid": false, "reason": "invalid_code", "attempts_left": 3 }
Valeurs de reason : invalid_code, expired, already_used, too_many_attempts, not_found.
GET /messages/:id
200 OK
{ "id": "6f1c…", "to": "client@exemple.fr", "status": "sent", "attempts": 1,
"error": null, "created_at": "…", "sent_at": "…" }
Modèles
Gérez vos modèles depuis votre propre panel d'administration :
GET /templates | Liste des modèles du projet. |
GET /templates/:nom | Contenu d'un modèle. |
PUT /templates/:nom | Crée ou remplace un modèle : {"subject", "html", "text"}. |
DELETE /templates/:nom | Supprime un modèle. |
Nom : lettres minuscules, chiffres, _ et - (64 caractères maximum). La balise {code} n'est interprétée que dans les modèles de vérification ; ailleurs, elle reste du texte.
Pièces jointes et logo
"attachments": [
{ "filename": "facture.pdf", "content_type": "application/pdf", "content": "JVBERi0xLjQK…" }
]
- 5 fichiers maximum, 5 Mo au total (après décodage base64).
- Types autorisés : PDF, PNG, JPEG, GIF, WebP, texte, CSV, iCalendar.
- Archives, exécutables, scripts et HTML sont refusés.
- Le logo (
logo_urlou logo du projet) doit être une URLhttps.
Idempotence
Pour éviter les doublons en cas de nouvelle tentative réseau, ajoutez l'en-tête Idempotency-Key (1 à 100 caractères : lettres, chiffres, . _ : -). Une requête répétée avec la même clé renvoie le message existant avec "duplicate": true au lieu d'en créer un nouveau.
Idempotency-Key: contact-form-8d2f61
Quotas
- Limite quotidienne et limite par minute propres à chaque projet (visibles dans
GET /projectet le tableau de bord). Valeurs par défaut : 500 par jour, 30 par minute. - 300 requêtes par minute et par adresse IP sur l'API.
- Un code de vérification par adresse et par minute.
- Contenu : 512 Ko maximum.
Erreurs
{ "error": "project_not_verified", "message": "L'adresse expéditrice de ce projet n'est pas validée…" }
| HTTP | Code | Cause |
|---|---|---|
| 400 | invalid_json, wrong_endpoint, wrong_project_type | Requête mal formée ou endpoint inadapté au type de projet. |
| 401 | missing_api_key, invalid_api_key | Clé absente, invalide ou révoquée. |
| 403 | project_not_verified, project_suspended, browser_request_forbidden | Projet non autorisé à envoyer, ou appel depuis un navigateur. |
| 404 | template_not_found, message_not_found | Ressource inconnue. |
| 413 | attachments_too_large, content_too_large | Limites de taille dépassées. |
| 422 | invalid_recipient, invalid_subject, missing_content, template_missing_code, attachment_type_forbidden, recipient_suppressed… | Données invalides. |
| 429 | rate_limited, daily_quota_exceeded, code_cooldown | Quota atteint. Réessayez plus tard. |
| 503 | service_unavailable | Service d'envoi indisponible : la demande n'a pas été enregistrée. Réessayez après le délai indiqué par Retry-After, idéalement avec la même Idempotency-Key. |
Exemples d'intégration
Node.js (serveur)
const res = await fetch('https://flamboyant-bouman.82-26-80-185.plesk.page/api/v1/verification/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PROPULSEMAIL_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: email }),
});
const { verification_id } = await res.json();
// Conservez verification_id en session, puis :
const check = await fetch('https://flamboyant-bouman.82-26-80-185.plesk.page/api/v1/verification/check', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.PROPULSEMAIL_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ verification_id, code: codeSaisi }),
}).then((r) => r.json());
if (check.valid) { /* adresse confirmée */ }
PHP (formulaire de contact)
$ch = curl_init('https://flamboyant-bouman.82-26-80-185.plesk.page/api/v1/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PROPULSEMAIL_KEY'),
'Content-Type: application/json',
'Idempotency-Key: contact-' . bin2hex(random_bytes(8)),
],
CURLOPT_POSTFIELDS => json_encode([
'to' => 'moi@monsite.fr',
'reply_to' => $_POST['email'],
'subject' => 'Nouveau message de contact',
'text' => $_POST['message'],
]),
]);
$response = json_decode(curl_exec($ch), true);
Python
import os, requests
r = requests.post(
"https://flamboyant-bouman.82-26-80-185.plesk.page/api/v1/send",
headers={"Authorization": f"Bearer {os.environ['PROPULSEMAIL_KEY']}"},
json={"to": ["a@exemple.fr", "b@exemple.fr"], "template": "newsletter-octobre"},
timeout=10,
)
print(r.status_code, r.json())