MailLandDocs
Envoi d'emails

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/send
Send an email
curl -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

Full request example
{
  "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

ChampTypeRequisDescription
fromobjectOuiAdresse de l'expéditeur
from.emailstringOuiE-mail de l'expéditeur (doit être sur un domaine vérifié)
from.namestringNonNom d'affichage
toarrayOuiListe des destinataires
to[].emailstringOuiAdresse e-mail du destinataire
to[].namestringNonNom d'affichage du destinataire
to[].variablesobjectNonVariables de modèle par destinataire
subjectstringConditionnelRequis sauf si un modèle est utilisé
templateIdstringNonUtiliser un modèle enregistré à la place du contenu en ligne
htmlContentstringConditionnelCorps HTML (htmlContent ou textContent requis si pas de templateId)
textContentstringNonTexte brut de secours
variablesobjectNonVariables 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
replyTostringNonAdresse e-mail de réponse
tagsarrayNonÉtiquettes pour le filtrage dans les analyses et webhooks
customHeadersobjectNonEn-têtes d'e-mail personnalisés (paires clé-valeur)
trackingobjectNonRemplacer les paramètres de suivi
tracking.opensbooleanNonDemander 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.clicksbooleanNonActiver le suivi des clics (par défaut : false pour les flux transactionnels sauf si votre compte l'active, true pour le marketing)
ipPoolIdstringNonRéservé aux pools d'IP dédiées. Pas encore disponible : l'envoyer renvoie 400 IP_POOL_NOT_FOUND. À omettre.
scheduledAtdatetimeNonPlanifier pour une livraison future (UTC), jusqu'à 7 jours à l'avance
idempotencyKeystringNonEmpêcher les envois en double pour la même clé. Préférez l'en-tête X-Idempotency-Key
attachmentsarrayNonPièces jointes (max 10, max 10 Mo décodés au total)
priorityenumNonLow, Normal (par défaut), High ou Critical

Pièces jointes

Chaque pièce jointe nécessite trois champs :

Attachment format
{
  "attachments": [
    {
      "fileName": "invoice.pdf",
      "contentType": "application/pdf",
      "content": "JVBERi0xLjQKJ...",
      "inline": false
    },
    {
      "fileName": "logo.png",
      "contentType": "image/png",
      "content": "iVBORw0KGgoAAAA...",
      "contentId": "company-logo",
      "inline": true
    }
  ]
}
ChampTypeRequisDescription
fileNamestringOuiNom du fichier avec extension
contentTypestringOuiType MIME (ex. application/pdf)
contentstringOuiContenu du fichier en Base64 standard : avec le padding, sans espace ni saut de ligne
contentIdstringNonID de contenu pour les pièces jointes en ligne (référencé en HTML sous cid:company-logo)
inlinebooleanNonS'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 response
{
  "success": true,
  "jobId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "messageIds": ["t_a1b2c3d4e5f6..."],
  "acceptedRecipients": ["[email protected]"],
  "rejectedRecipients": [],
  "queuedAt": "2026-02-17T10:30:00Z",
  "isSandboxed": false
}
ChampTypeDescription
successbooleanSi la requête a été acceptée
jobIdstringIdentifiant unique du travail pour le suivi
messageIdsarrayID de message par destinataire (préfixe t_ pour transactionnel, m_ pour marketing)
acceptedRecipientsarrayAdresses e-mail acceptées pour la livraison
rejectedRecipientsarrayDestinataires refusés
queuedAtdatetimeQuand l'e-mail a été mis en file d'attente
isSandboxedbooleantrue si la requête a utilisé un identifiant sandbox (voir Mode Sandbox)
sandboxReasonstringcredential_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 :

Response with rejected recipients
{
  "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 champ suggestion)

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=1
Pre-flight check
curl https://api.mail.land/v1/transactional/send/check?recipientCount=1 \
  -H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."
Pre-flight response
{
  "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-v1

Ou 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-Key n'est accepté que sur POST /v1/transactional/send et POST /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-12345

ou

X-Request-ID: my-app-order-12345

L'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: 295

En 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: 30

Une 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 :

Error response format
{
  "code": "ERROR_CODE",
  "message": "Human-readable description",
  "timestamp": "2026-02-17T10:30:00Z",
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}

Codes d'erreur

StatutCodeDescription
400INVALID_REQUESTCorps de requête malformé, ou scheduledAt de plus de 7 jours à l'avance (« Cannot schedule email more than 7 days in advance »)
400VALIDATION_FAILEDPas 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.)
413CONTENT_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
400transactional_recipient_limit_exceededPlus de 5 destinataires sur un identifiant transactionnel (1 000 en marketing)
400TEMPLATE_NOT_FOUNDtemplateId ne désigne aucun modèle actif de ce compte (également renvoyé pour un modèle supprimé)
400IP_POOL_NOT_FOUNDipPoolId 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)
422TEMPLATE_VARIABLE_MISSINGLe contenu a un {{placeholder}} sans valeur ; details.missingVariables liste les noms. Rien n'est mis en file
400IDEMPOTENCY_KEY_INVALIDClé vide, mal formée, de plus de 255 caractères, ou en-tête envoyé plusieurs fois
400IDEMPOTENCY_KEY_CONFLICTL'en-tête et le champ idempotencyKey ont tous deux été fournis et diffèrent
400SEND_FAILEDTous les destinataires sont supprimés (message : « All recipients are suppressed »)
409IDEMPOTENCY_KEY_REUSEDLa clé est déjà utilisée pour un autre endpoint de ce compte
401UNAUTHORIZEDX-API-Key manquante, invalide, expirée ou révoquée
403ACCOUNT_SUSPENDEDCompte suspendu
403ACCOUNT_DISABLEDCompte désactivé
403ACCOUNT_NOT_ACTIVECompte non actif
403NO_ACTIVE_SUBSCRIPTIONLe compte n'a pas d'abonnement actif
403SUBSCRIPTION_EXPIREDL'abonnement du compte a expiré
403IP_NOT_ALLOWEDIP de la requête absente de la liste autorisée des identifiants
403SENDING_BLOCKEDL'envoi est bloqué pour ce compte
403ACTION_REQUIREDUne action sur le compte est requise avant l'envoi
403TENANT_APPROVAL_REQUIREDLe compte est en attente de revue avant de pouvoir envoyer. Réessayer n'aide pas ; contactez le support
422DOMAIN_NOT_VERIFIEDDomaine expéditeur non vérifié
422DOMAIN_NOT_ALLOWED_FOR_CREDENTIALDomaine hors de la portée des identifiants
422DOMAIN_NOT_FOUNDDomaine expéditeur introuvable
429RATE_LIMIT_EXCEEDEDLimite de débit dépassée
429QUOTA_EXCEEDEDQuota mensuel épuisé
429WARMUP_LIMIT_EXCEEDEDLa limite quotidienne de montée en charge de l'envoi du compte est atteinte ; réinitialisation à 00:00 UTC
429DAILY_LIMIT_EXCEEDEDLimite d'envoi quotidienne atteinte
429WARMUP_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."
429WARMUP_POOL_EMPTY"Sending is temporarily paused. It resumes automatically; no action is needed."
429PLATFORM_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."
429PLATFORM_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."
429PROBATION_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."
429PAYMENT_DAILY_LIMIT_EXCEEDEDLimite quotidienne atteinte sur un compte en retard de paiement
429MARKETING_PAUSEDEnvoi marketing en pause pour ce compte
429SENDING_BLOCKEDLe 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)
503SERVICE_UNAVAILABLEService 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é :

Send with a template
{
  "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

Sur cette page