API REST
Envoyez des e-mails via l'API REST Mail.Land avec une référence complète des requêtes et réponses
Envoyez des e-mails transactionnels via POST /v1/transactional/send. C'est la méthode principale pour envoyer des e-mails via MailLand.
Authentification
Incluez votre clé API dans l'en-tête X-API-Key :
X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD...Les identifiants doivent avoir le canal Api activé et être des identifiants de produit Sending.
Envoyer un e-mail unique
POST https://api.mail.land/v1/transactional/sendcurl -X POST https://api.mail.land/v1/transactional/send \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..." \
-H "Content-Type: application/json" \
-d '{
"from": {"email": "[email protected]"},
"to": [{"email": "[email protected]"}],
"subject": "Hello from MailLand",
"htmlContent": "<p>Your email content here</p>"
}'Corps de la requête
{
"from": {
"email": "[email protected]",
"name": "Your App"
},
"to": [
{
"email": "[email protected]",
"name": "Jane Doe",
"variables": {
"firstName": "Jane",
"orderNumber": "ORD-12345"
}
}
],
"subject": "Your order #{{orderNumber}} has shipped",
"htmlContent": "<h1>Hi {{firstName}},</h1><p>Your order #{{orderNumber}} is on its way.</p>",
"textContent": "Hi {{firstName}}, Your order #{{orderNumber}} is on its way.",
"replyTo": "[email protected]",
"tags": ["order-shipped", "transactional"],
"customHeaders": {
"X-Entity-Ref-ID": "order-12345"
},
"tracking": {
"opens": true,
"clicks": true
},
"priority": "High",
"idempotencyKey": "order-12345-shipped-v1"
}Référence des champs
| Champ | Type | Requis | Description |
|---|---|---|---|
from | object | Oui | Adresse de l'expéditeur |
from.email | string | Oui | E-mail de l'expéditeur (doit être sur un domaine vérifié) |
from.name | string | Non | Nom d'affichage |
to | array | Oui | Liste des destinataires |
to[].email | string | Oui | Adresse e-mail du destinataire |
to[].name | string | Non | Nom d'affichage du destinataire |
to[].variables | object | Non | Variables de modèle par destinataire |
subject | string | Conditionnel | Requis sauf si un modèle est utilisé |
templateId | string | Non | Utiliser un modèle enregistré à la place du contenu en ligne |
htmlContent | string | Conditionnel | Corps HTML (htmlContent ou textContent requis si pas de templateId) |
textContent | string | Non | Texte brut de secours |
variables | object | Non | Variables de modèle globales. Appliquées que templateId soit défini ou non ; en cas de clé identique, la valeur propre du destinataire dans to[].variables l'emporte |
replyTo | string | Non | Adresse e-mail de réponse |
tags | array | Non | Étiquettes pour le filtrage dans les analyses et webhooks |
customHeaders | object | Non | En-têtes d'e-mail personnalisés (paires clé-valeur) |
tracking | object | Non | Remplacer les paramètres de suivi |
tracking.opens | boolean | Non | Demander le suivi des ouvertures pour ce message (par défaut : désactivé sur les deux flux). Utilisé seulement tant que le domaine d'envoi n'a pas choisi ; le choix du domaine par flux, fait sur la page Domaines, l'emporte. false le désactive toujours |
tracking.clicks | boolean | Non | Activer le suivi des clics (par défaut : false pour les flux transactionnels sauf si votre compte l'active, true pour le marketing) |
ipPoolId | string | Non | Réservé aux pools d'IP dédiées. Pas encore disponible : l'envoyer renvoie 400 IP_POOL_NOT_FOUND. À omettre. |
scheduledAt | datetime | Non | Planifier pour une livraison future (UTC), jusqu'à 7 jours à l'avance |
idempotencyKey | string | Non | Empêcher les envois en double pour la même clé. Préférez l'en-tête X-Idempotency-Key |
attachments | array | Non | Pièces jointes (max 10, max 10 Mo décodés au total) |
priority | enum | Non | Low, Normal (par défaut), High ou Critical |
Pièces jointes
Chaque pièce jointe nécessite trois champs :
{
"attachments": [
{
"fileName": "invoice.pdf",
"contentType": "application/pdf",
"content": "JVBERi0xLjQKJ...",
"inline": false
},
{
"fileName": "logo.png",
"contentType": "image/png",
"content": "iVBORw0KGgoAAAA...",
"contentId": "company-logo",
"inline": true
}
]
}| Champ | Type | Requis | Description |
|---|---|---|---|
fileName | string | Oui | Nom du fichier avec extension |
contentType | string | Oui | Type MIME (ex. application/pdf) |
content | string | Oui | Contenu du fichier en Base64 standard : avec le padding, sans espace ni saut de ligne |
contentId | string | Non | ID de contenu pour les pièces jointes en ligne (référencé en HTML sous cid:company-logo) |
inline | boolean | Non | S'il s'agit d'une image en ligne (par défaut : false) |
Au maximum 10 pièces jointes par e-mail et au maximum 10 Mo décodés (après Base64) au total. Un contentType manquant, un Base64 non canonique, ou un dépassement du nombre ou de la taille autorisés renvoie 400 VALIDATION_FAILED. POST /v1/transactional/send-batch n'a pas de champ attachments.
Réponse
Statut : 202 Accepted
{
"success": true,
"jobId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"messageIds": ["t_a1b2c3d4e5f6..."],
"acceptedRecipients": ["[email protected]"],
"rejectedRecipients": [],
"queuedAt": "2026-02-17T10:30:00Z",
"isSandboxed": false
}| Champ | Type | Description |
|---|---|---|
success | boolean | Si la requête a été acceptée |
jobId | string | Identifiant unique du travail pour le suivi |
messageIds | array | ID de message par destinataire (préfixe t_ pour transactionnel, m_ pour marketing) |
acceptedRecipients | array | Adresses e-mail acceptées pour la livraison |
rejectedRecipients | array | Destinataires refusés |
queuedAt | datetime | Quand l'e-mail a été mis en file d'attente |
isSandboxed | boolean | true si la requête a utilisé un identifiant sandbox (voir Mode Sandbox) |
sandboxReason | string | credential_sandbox_mode (journalisé, non livré) ou all_recipients_not_in_sandbox_allowlist (aucun destinataire dans la liste d'autorisation) ; absent sinon |
Destinataires rejetés
Les destinataires peuvent être rejetés pour plusieurs raisons :
{
"success": true,
"jobId": "...",
"acceptedRecipients": ["[email protected]"],
"rejectedRecipients": [
{
"email": "[email protected]",
"reason": "Suppressed"
},
{
"email": "[email protected]",
"reason": "domain_typo",
"suggestion": "[email protected]"
}
]
}Raisons de rejet courantes :
Suppressed: adresse sur la liste de suppression (rebonds/plaintes précédents)disposable_domain: fournisseur d'e-mail jetable détectédomain_typo: faute de frappe probable dans le domaine (inclut le champsuggestion)
Vérification préalable
Vérifiez votre capacité d'envoi avant de faire la requête réelle :
GET https://api.mail.land/v1/transactional/send/check?recipientCount=1curl https://api.mail.land/v1/transactional/send/check?recipientCount=1 \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."{
"canSend": true,
"rateLimitStatus": {
"currentSecond": 2,
"limitPerSecond": 50,
"currentHour": 450,
"limitPerHour": 5000,
"currentDay": 8200,
"limitPerDay": 50000
},
"monthlyUsed": 125000,
"monthlyLimit": 500000
}Si canSend est false, le champ reason explique pourquoi (limite de débit, quota, statut du compte, etc.).
Idempotence
Réessayer un envoi après une expiration réseau ne doit pas livrer l'e-mail deux fois. Avec une clé d'idempotence, une nouvelle tentative rejoue la réponse d'origine au lieu de renvoyer.
Envoyez la clé en en-tête (recommandé) :
X-Idempotency-Key: order-12345-shipped-v1Ou utilisez le champ idempotencyKey du corps. Les deux sont acceptés, et vous pouvez envoyer les deux uniquement si les valeurs sont identiques.
Règles
- Portée.
X-Idempotency-Keyn'est accepté que surPOST /v1/transactional/sendetPOST /v1/transactional/send-batch. Aucun autre endpoint ne le lit. - Format. 1 à 255 caractères : lettres, chiffres,
_et-. Les clés sont comparées exactement, sans rognage ni normalisation de casse. - Fenêtre. Une clé reste rejouable pendant 24 heures. Ensuite, la même clé est traitée comme une nouvelle requête.
- Rejeu. Une nouvelle tentative dans la fenêtre renvoie la réponse d'origine telle quelle (mêmes identifiants, même statut) et n'envoie rien.
- Unicité. Les clés sont uniques par compte. Réutiliser une clé pour un autre endpoint est rejeté avec
409; utilisez une clé distincte par opération.
ID de corrélation
Suivez les requêtes entre les systèmes en incluant un ID de corrélation :
X-Correlation-ID: my-app-order-12345ou
X-Request-ID: my-app-order-12345L'ID de corrélation apparaît dans les journaux et le flux d'activité.
En-têtes de limite de débit
Les réponses réussies incluent des informations de limite de débit par IP :
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 295En cas de limitation de débit (429) par le moteur de politique d'envoi, un en-tête Retry-After indique le nombre de secondes à attendre :
Retry-After: 30Une requête transactionnelle peut nommer jusqu'à 5 destinataires, et chacun compte dans vos limites de taux. Si c'est plus que votre limite par seconde (par exemple 1 par seconde avec un forfait de 60 par minute), la requête est acceptée lorsqu'aucune autre requête n'a été acceptée dans la même seconde ; sinon elle reçoit 429 avec Retry-After: 1 et réussit si elle est relancée la seconde suivante, dans la limite par minute. Voir Limites de taux.
Un 429 au niveau IP inclut aussi X-RateLimit-Reset sous forme d'heure UTC ISO-8601.
Réponses d'erreur
Toutes les erreurs renvoient un corps JSON :
{
"code": "ERROR_CODE",
"message": "Human-readable description",
"timestamp": "2026-02-17T10:30:00Z",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Codes d'erreur
| Statut | Code | Description |
|---|---|---|
400 | INVALID_REQUEST | Corps de requête malformé, ou scheduledAt de plus de 7 jours à l'avance (« Cannot schedule email more than 7 days in advance ») |
400 | VALIDATION_FAILED | Pas de htmlContent, textContent ou templateId ; ou une pièce jointe échoue à la validation (voir les limites des pièces jointes ci-dessus). La réponse inclut un tableau errors[] avec le champ et la règle en échec. (Auparavant : MISSING_CONTENT : déprécié depuis 2026-04.) |
413 | CONTENT_TOO_LARGE | "The message is too large to send: it would exceed the platform's message-size limit. Reduce the HTML, text, attachments or recipients." : Le message dépasse la limite de taille de la plateforme. POST /v1/transactional/send et send-batch le renvoient en 413 ; rien n'est mis en file. Réduisez le message. Pour l'import CSV, c'est 400 |
400 | transactional_recipient_limit_exceeded | Plus de 5 destinataires sur un identifiant transactionnel (1 000 en marketing) |
400 | TEMPLATE_NOT_FOUND | templateId ne désigne aucun modèle actif de ce compte (également renvoyé pour un modèle supprimé) |
400 | IP_POOL_NOT_FOUND | ipPoolId ne désigne aucun pool assigné à ce compte (actuellement toujours, car aucun pool ne peut encore être assigné : voir la référence des champs ci-dessus) |
422 | TEMPLATE_VARIABLE_MISSING | Le contenu a un {{placeholder}} sans valeur ; details.missingVariables liste les noms. Rien n'est mis en file |
400 | IDEMPOTENCY_KEY_INVALID | Clé vide, mal formée, de plus de 255 caractères, ou en-tête envoyé plusieurs fois |
400 | IDEMPOTENCY_KEY_CONFLICT | L'en-tête et le champ idempotencyKey ont tous deux été fournis et diffèrent |
400 | SEND_FAILED | Tous les destinataires sont supprimés (message : « All recipients are suppressed ») |
409 | IDEMPOTENCY_KEY_REUSED | La clé est déjà utilisée pour un autre endpoint de ce compte |
401 | UNAUTHORIZED | X-API-Key manquante, invalide, expirée ou révoquée |
403 | ACCOUNT_SUSPENDED | Compte suspendu |
403 | ACCOUNT_DISABLED | Compte désactivé |
403 | ACCOUNT_NOT_ACTIVE | Compte non actif |
403 | NO_ACTIVE_SUBSCRIPTION | Le compte n'a pas d'abonnement actif |
403 | SUBSCRIPTION_EXPIRED | L'abonnement du compte a expiré |
403 | IP_NOT_ALLOWED | IP de la requête absente de la liste autorisée des identifiants |
403 | SENDING_BLOCKED | L'envoi est bloqué pour ce compte |
403 | ACTION_REQUIRED | Une action sur le compte est requise avant l'envoi |
403 | TENANT_APPROVAL_REQUIRED | Le compte est en attente de revue avant de pouvoir envoyer. Réessayer n'aide pas ; contactez le support |
422 | DOMAIN_NOT_VERIFIED | Domaine expéditeur non vérifié |
422 | DOMAIN_NOT_ALLOWED_FOR_CREDENTIAL | Domaine hors de la portée des identifiants |
422 | DOMAIN_NOT_FOUND | Domaine expéditeur introuvable |
429 | RATE_LIMIT_EXCEEDED | Limite de débit dépassée |
429 | QUOTA_EXCEEDED | Quota mensuel épuisé |
429 | WARMUP_LIMIT_EXCEEDED | La limite quotidienne de montée en charge de l'envoi du compte est atteinte ; réinitialisation à 00:00 UTC |
429 | DAILY_LIMIT_EXCEEDED | Limite d'envoi quotidienne atteinte |
429 | WARMUP_POOL_BUDGET_EXCEEDED | "Sending is temporarily paused for today. It resumes automatically at 00:00 UTC; no action is needed." / "Sending is briefly delayed. Please retry in a few minutes." |
429 | WARMUP_POOL_EMPTY | "Sending is temporarily paused. It resumes automatically; no action is needed." |
429 | PLATFORM_DAILY_CAPACITY_REACHED | "Platform sending capacity for today is fully booked. This is a temporary platform-wide limit, not your account quota, and it resets at midnight UTC." |
429 | PLATFORM_CAPACITY_PROTECTION | "New sending is briefly paused to protect platform capacity. Mail already accepted is not affected. This is transient and clears on its own; retry shortly." |
429 | PROBATION_CAP_REACHED | "New accounts can send to N recipients per rolling 24 hours until our team has reviewed the account. Capacity returns as earlier sends age out of the 24-hour window; contact support to request a review." |
429 | PAYMENT_DAILY_LIMIT_EXCEEDED | Limite quotidienne atteinte sur un compte en retard de paiement |
429 | MARKETING_PAUSED | Envoi marketing en pause pour ce compte |
429 | SENDING_BLOCKED | Le niveau de vérification du compte n'autorise pas cet envoi (refus liés au sandbox/niveau de vérification ; un blocage d'envoi strict renvoie plutôt 403, voir ci-dessus) |
503 | SERVICE_UNAVAILABLE | Service e-mail temporairement indisponible |
Limite de destinataires transactionnels
Les identifiants transactionnels imposent un maximum de 5 destinataires par requête d'envoi. Cela empêche l'utilisation abusive des flux transactionnels pour l'envoi en masse. Si vous avez besoin de plus de destinataires, utilisez un identifiant Marketing avec l'envoi par lots.
Utilisation des modèles
Au lieu du HTML en ligne, référencez un modèle enregistré :
{
"from": { "email": "[email protected]" },
"to": [
{
"email": "[email protected]",
"variables": {
"firstName": "Jane",
"resetLink": "https://app.example.com/reset?token=abc123"
}
}
],
"templateId": "t8x2m4q9zk7wpr",
"variables": {
"companyName": "Acme Inc"
}
}Le subject provient du modèle. En cas de clé identique, la valeur propre du destinataire dans to[].variables remplace les variables globales : la même règle que pour le contenu en ligne. Consultez Modèles pour créer et gérer des modèles.
Étapes suivantes
- Envoi par lots : envoyer à plusieurs destinataires en une seule fois
- Relais SMTP : envoyer via SMTP plutôt que REST
- Modèles : créer des modèles d'e-mail réutilisables
- E-mails planifiés : planifier des e-mails pour une livraison future