Aller au contenu

← Tous les articles

Envoyer par SMTP avec Duva : réglages, code et erreurs courantes

Tout n'a pas besoin d'une bibliothèque. Un cadriciel, un système de gestion de contenu ou un fournisseur d'authentification sait déjà envoyer par SMTP : il suffit de lui donner une adresse de serveur, un utilisateur et un mot de passe. Duva reçoit les messages par SMTP en plus de l'API HTTPS, avec les mêmes règles. Voici comment le configurer, ce que le serveur lit de votre message, et comment interpréter ses réponses.

SMTP ou API ?

Les deux chemins mènent à la même file d'envoi et aux mêmes endroits de lecture : le message se retrouve par GET /messages/{id}, dans les événements, les webhooks et les statistiques. Un message reçu par SMTP suit les mêmes règles que celui de l'API : domaine vérifié, quota, bac à sable, adresses supprimées et idempotence.

  • Choisissez SMTP quand le logiciel envoie déjà par SMTP et que vous voulez seulement changer de serveur : un site WordPress, Laravel, Django, Rails, un fournisseur d'authentification.
  • Choisissez l'API quand vous écrivez le code d'envoi vous-même. Elle répond tout de suite avec l'identifiant et le statut, et les bibliothèques officielles (Node.js, Python, Ruby, Go, PHP) gèrent les nouvelles tentatives pour vous.

Rien ne vous enferme : vous pouvez commencer par SMTP et passer à l'API plus tard, sur le même domaine et avec la même clé.

Les réglages

Réglage Valeur
Serveur smtp.duva.ca
Port 587 STARTTLS (recommandé)
Port 465 TLS implicite
Utilisateur votre domaine d'envoi, par exemple example.com
Mot de passe la clé API de ce domaine (dv_...)
Authentification PLAIN ou LOGIN, après le chiffrement

Trois points à retenir :

  • L'utilisateur est le domaine, pas une adresse de courriel. C'est l'erreur la plus fréquente : [email protected] ne fonctionne pas, example.com oui.
  • Le chiffrement est obligatoire. Aucune authentification et aucun message ne passent en clair, et le port 25 n'est pas offert.
  • La clé est vérifiée à chaque message. Une clé révoquée ou expirée cesse de fonctionner tout de suite, même sur une connexion déjà ouverte. Quelle que soit la cause d'un refus d'authentification, la réponse est la même (535) : elle ne dit pas laquelle.

Le domaine doit d'abord être vérifié, comme pour l'API. Les étapes DNS sont décrites dans SPF, DKIM et DMARC : configurer un domaine d'envoi.

Un premier envoi en Python

La bibliothèque standard suffit. La clé API vient de l'environnement.

import json
import os
import smtplib
import ssl
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "Example <[email protected]>"
msg["To"] = "[email protected]"
msg["Subject"] = "Votre commande"
msg["X-Duva-Tags"] = "commande,confirmation"
msg["X-Duva-Metadata"] = json.dumps({"order_id": "4821"})
msg["X-Idempotency-Key"] = "order-4821"
msg.set_content("Merci pour votre commande.")

with smtplib.SMTP("smtp.duva.ca", 587, timeout=30) as smtp:
    smtp.starttls(context=ssl.create_default_context())
    smtp.login("example.com", os.environ["DUVA_API_KEY"])
    print(smtp.send_message(msg))

send_message renvoie un dictionnaire vide quand tous les destinataires sont acceptés. Une exception (SMTPRecipientsRefused, SMTPSenderRefused, SMTPAuthenticationError) indique un refus : voir le tableau plus bas.

Et en Node.js, avec Nodemailer

import nodemailer from "nodemailer";

const transport = nodemailer.createTransport({
  host: "smtp.duva.ca",
  port: 587,
  requireTLS: true,
  auth: { user: "example.com", pass: process.env.DUVA_API_KEY },
});

const info = await transport.sendMail({
  from: "Example <[email protected]>",
  to: "[email protected]",
  subject: "Votre commande",
  text: "Merci pour votre commande.",
  headers: {
    "X-Duva-Tags": "commande,confirmation",
    "X-Duva-Metadata": JSON.stringify({ order_id: "4821" }),
    "X-Idempotency-Key": "order-4821",
  },
});
console.log(info.response);

requireTLS: true refuse d'envoyer si le serveur n'offre pas le chiffrement, ce qui est le comportement voulu.

Les cadriciels

Pour Django, les réglages tiennent en cinq lignes :

EMAIL_HOST = "smtp.duva.ca"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "example.com"
EMAIL_HOST_PASSWORD = os.environ["DUVA_API_KEY"]
DEFAULT_FROM_EMAIL = "[email protected]"

Pour Laravel, dans .env :

MAIL_MAILER=smtp
MAIL_HOST=smtp.duva.ca
MAIL_PORT=587
MAIL_USERNAME=example.com
MAIL_PASSWORD=dv_...
[email protected]

Rails, WordPress et les fournisseurs d'authentification suivent le même modèle : serveur, port 587, utilisateur = domaine, mot de passe = clé. La référence donne aussi l'exemple Rails. Pour les cadriciels qui ont une intégration officielle, elle donne accès aux étiquettes, aux métadonnées et au suivi sans écrire d'en-têtes.

Ce que le serveur lit de votre message

  • Les destinataires sont ceux de l'enveloppe SMTP (RCPT TO). Les en-têtes To et Cc ne servent qu'à classer ces adresses ; toute autre adresse de l'enveloppe reçoit une copie cachée. Un en-tête Bcc n'est jamais lu. Vous pouvez avoir 100 destinataires au plus par transaction, et le plafond du forfait de votre compte s'applique.
  • L'expéditeur est l'en-tête From, qui doit appartenir au domaine de la clé (sinon 553). L'adresse d'enveloppe (MAIL FROM) est ignorée : Duva utilise sa propre adresse de retour.
  • Le contenu : sujet, texte, HTML, Reply-To et pièces jointes, avec les mêmes limites que l'API. Un compte en bac à sable ne peut pas envoyer de pièces jointes.
  • La taille : 10 Mo au plus par message (552).
  • Les messages signés ou chiffrés (S/MIME, PGP/MIME) sont refusés. Duva reconstruit chaque message, ce qui casserait votre signature ; la signature DKIM qui compte est celle de Duva.

Quatre en-têtes propres à Duva

Ils sont lus, puis retirés : le destinataire ne les voit jamais.

En-tête Rôle
X-Duva-Tags Étiquettes, séparées par des virgules.
X-Duva-Metadata Métadonnées sous forme d'objet JSON (valeurs en texte).
X-Duva-Track opens, clicks ou les deux, séparés par une virgule (le suivi doit être activé pour le domaine).
X-Idempotency-Key Clé d'idempotence.

Les réponses du serveur

250 signifie que le message est accepté pour livraison : il est en file, pas livré. L'identifiant msg_... est dans la réponse ; gardez-le pour suivre le message par l'API, les événements ou les webhooks (voir Un courriel n'est pas arrivé). Duva n'envoie aucun courriel de non-remise à l'expéditeur : un rebond n'apparaît que dans les événements.

Réponse Signification
250 Accepté pour livraison.
451 Réessayez plus tard (limite de débit ou panne passagère).
452 Trop de destinataires dans cette transaction.
454 Trop d'essais d'authentification : attendez.
530 Authentification ou chiffrement requis.
535 Identifiants invalides (toute cause).
550 Refusé : quota atteint, envoi impossible pour ce compte ou ce domaine, ou conflit d'idempotence.
552 Message trop gros.
553 Expéditeur hors du domaine de la clé, ou adresse invalide.
554 Contenu invalide (par exemple, sans corps) ou destinataire invalide.

Une réponse 4xx invite à réessayer ; une réponse 5xx est définitive pour ce message. Les logiciels d'envoi respectent déjà cette convention.

Les nouvelles tentatives sans doublon

Sans X-Idempotency-Key, Duva en calcule une à partir du Message-ID et des destinataires. Un logiciel qui renvoie le même message après une coupure ne crée donc pas de doublon, et reçoit la même réponse. Le même Message-ID pour d'autres destinataires est un autre message. Si votre logiciel génère un nouveau Message-ID à chaque tentative, posez votre propre clé avec X-Idempotency-Key.

Les erreurs qui reviennent

  • 535 alors que la clé est bonne : l'utilisateur est une adresse de courriel au lieu du domaine, ou la clé appartient à un autre domaine.
  • 553 : l'adresse From est d'un autre domaine que celui de la clé. Un logiciel qui remplace l'expéditeur (par exemple un plugin de formulaire) peut en être la cause.
  • 530 : le chiffrement n'est pas activé dans le logiciel. Sur le port 587, il faut STARTTLS ; sur le port 465, le TLS implicite.
  • Un message accepté mais jamais reçu : 250 ne dit rien de la suite. Lisez le statut du destinataire et ses événements.
  • Un message envoyé avec succès, mais sans étiquettes ni métadonnées : le nom de l'en-tête est mal orthographié, ou les métadonnées ne sont pas un objet JSON.

Une liste de vérification

  • Le domaine d'envoi est vérifié.
  • L'utilisateur est le domaine, le mot de passe est la clé du même domaine.
  • Le port est 587 avec STARTTLS, ou 465 avec TLS.
  • L'adresse From appartient au domaine.
  • Le logiciel garde l'identifiant msg_... de la réponse.
  • La clé est dans une variable d'environnement, pas dans le code.
  • Vous lisez les rebonds par l'API ou un webhook dont vous vérifiez la signature.