PropulseMail

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
Votre clé doit rester sur votre serveur. Ne l'intégrez jamais dans du JavaScript exécuté par le navigateur ni dans une application mobile. Les requêtes émises par un navigateur (en-tête 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 :

StatutSignification
acceptedDemande acceptée et mise en file d'attente.
pendingEn cours d'envoi ou en attente d'une nouvelle tentative.
sentRemis 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.

ChampTypeDescription
tochaîne ou tableauDestinataire. Jusqu'à 50 adresses pour notification et newsletter ; une seule pour contact. Chaque destinataire reçoit un message séparé.
subjectchaîneSujet (200 caractères maximum). Facultatif si un modèle est utilisé.
htmlchaîneContenu HTML (balises autorisées ci-dessous).
textchaîneVersion texte brut. Générée automatiquement à partir du HTML si absente.
templatechaîneNom d'un modèle enregistré, à la place de subject/html/text.
variablesobjetValeurs pour les balises {{nom}} du contenu ou du modèle (échappées en HTML).
reply_tochaîneAdresse de réponse (utile pour les formulaires de contact).
logo_urlchaîneURL https d'un logo, affiché en haut de l'e-mail. Par défaut : logo du projet.
attachmentstableauVoir 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 /templatesListe des modèles du projet.
GET /templates/:nomContenu d'un modèle.
PUT /templates/:nomCrée ou remplace un modèle : {"subject", "html", "text"}.
DELETE /templates/:nomSupprime 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_url ou logo du projet) doit être une URL https.

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 /project et 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…" }
HTTPCodeCause
400invalid_json, wrong_endpoint, wrong_project_typeRequête mal formée ou endpoint inadapté au type de projet.
401missing_api_key, invalid_api_keyClé absente, invalide ou révoquée.
403project_not_verified, project_suspended, browser_request_forbiddenProjet non autorisé à envoyer, ou appel depuis un navigateur.
404template_not_found, message_not_foundRessource inconnue.
413attachments_too_large, content_too_largeLimites de taille dépassées.
422invalid_recipient, invalid_subject, missing_content, template_missing_code, attachment_type_forbidden, recipient_suppressed…Données invalides.
429rate_limited, daily_quota_exceeded, code_cooldownQuota atteint. Réessayez plus tard.
503service_unavailableService 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())