marten-mail
= marten-mail :toc: :toc-title: Sommaire
Envoi des courriels par SMTP pour https://martenframework.com[Marten], sans dépendance : rien d'autre que la bibliothèque standard de Crystal.
== Pourquoi
Marten ne fournit en standard que son envoi de développement (Marten::Emailing::Backend::Development), qui garde les courriels en mémoire ou les affiche, mais n'envoie rien. Sans autre réglage, un projet en production perd tous ses courriels sans la moindre erreur.
marten-mail fournit l'envoi réel, par un client SMTP écrit en Crystal : pas de sendmail, pas de relais du serveur, pas de logiciel à installer. Chaque projet envoie avec son compte SMTP, distinct du relais système (alertes de monit…).
== Ce que contient le shard
MartenMail::Client: client SMTP minimal (EHLO, STARTTLS ou TLS direct, AUTH PLAIN, MAIL, RCPT, DATA, une connexion par message) ;MartenMail::Message: message MIME (UTF-8, texte et/ou HTML, pièces jointes en base64, en-têtes encodés, aucune injection d'en-tête possible) ;MartenMail::Backend: envoi desMarten::Emaild'un projet par ce client.
require "marten-mail" charge le tout ; require "marten-mail/client" charge le client et le message sans Marten, pour un programme qui n'est pas une application Marten.
== Installation
[source,yaml]
dependencies: marten-mail: github: aloli-crystal/marten-mail branch: production
Le shard ne déclare aucune dépendance d'exécution : l'envoi pour Marten suppose que le projet dépend déjà de Marten.
== Utilisation avec Marten
[source,crystal]
require "marten-mail"
Marten.configure :production do |config| config.emailing.from_address = "notifications@example.net" config.emailing.backend = MartenMail::Backend.new(ENV["SMTP_URL"]) end
Tout Marten::Email du projet part alors par SMTP. Une adresse écrite "Nom <adresse>" (from "Exemple <notifications@example.net>") est séparée en nom et adresse. Les copies cachées (bcc) ne servent qu'à l'enveloppe SMTP, jamais aux en-têtes. Un refus du serveur lève MartenMail::DeliveryError.
== Utilisation sans Marten
[source,crystal]
require "marten-mail/client"
client = MartenMail::Client.from_url(ENV["SMTP_URL"]) message = MartenMail::Message.new( from: "notifications@example.net", from_name: "Exemple", to: ["client@example.org"], subject: "Bienvenue", text: "Bonjour…", html: "
Bonjour…
") message.reply_to = "contact@example.org" client.deliver(message)== URL SMTP
[cols="3,4",options="header"] |=== |URL |Connexion
|smtp://utilisateur:secret@hôte:587 |Port 587, STARTTLS (défaut de smtp://)
|smtps://utilisateur:secret@hôte:465 |Port 465, TLS direct
|smtp://hôte:587?security=tls |TLS direct sur un autre port
|smtp://localhost:25?security=none |Sans chiffrement (relais local seulement)
|…?helo=serveur.example.net |Nom annoncé par EHLO (défaut : localhost)
|…?timeout=10 |Délai de chaque étape, en secondes (défaut : 30) |===
Garde-fous. smtp:// sur le port 465 sans security explicite passe en TLS direct : c'est ce que ce port attend (piège courant, par exemple smtp://…@smtp.mail.ovh.net:465). smtps:// sur 25 ou 587, ports de STARTTLS, est refusé dès la lecture de l'URL, avec la correction à faire.
L'identifiant et le secret sont encodés en URL : @ → %40, : → %3A, / → %2F, % → %25. Exemple : l'identifiant notifications@example.net s'écrit notifications%40example.net.
Sécurité. Un mot de passe n'est jamais envoyé en clair sur le réseau : avec security=none, l'authentification n'est acceptée que vers la machine elle-même (localhost, 127.0.0.1, ::1), et refusée avant toute connexion sinon. Le certificat du serveur est vérifié (STARTTLS et TLS direct). Les messages d'erreur reprennent la réponse du serveur, jamais le secret.
== Vérifier un compte
marten-mail-check essaie un compte et dit s'il fonctionne, étape par étape. L'URL est lue sur l'entrée standard, jamais en argument (elle contient le mot de passe) ; au clavier, la saisie n'est pas affichée.
[source,sh]
shards build marten-mail-check --no-debug bin/marten-mail-check
[source]
URL SMTP (non affichée) : SMTP smtp.mail.ovh.net:465 (TLS direct) identifiant : notifications@example.net mot de passe : 16 caractère(s) ✓ connexion ✓ accueil du serveur — 220 … ✓ EHLO — 250-… ✓ authentification — 235 2.7.0 Authentication successful Le compte fonctionne : connexion, chiffrement et authentification réussis (aucun courriel envoyé).
La longueur du mot de passe décodé permet de repérer un caractère mal encodé qui coupe l'URL (#, ?, /…). Sans option, rien n'est envoyé ; --envoyer adresse envoie en plus un courriel de test (--de adresse : expéditeur, sinon l'identifiant du compte). Code de sortie : 0 le compte fonctionne, 1 échec (avec l'étape et la cause), 2 URL ou option invalide.
Depuis un programme : MartenMail::Client#check fait le même essai, et on_step reçoit chaque étape réussie.
== Erreurs
Tout échec lève MartenMail::DeliveryError avec le serveur, le chiffrement, l'étape et, quand elle est reconnaissable, la cause probable :
[source]
SMTP mail.example.net:2465 (STARTTLS), étape « accueil du serveur » : pas de réponse en 30 s — ce port attend sans doute une connexion TLS directe : écrivez smtps://mail.example.net:2465 SMTP mail.example.net:587 (STARTTLS) : le serveur n'annonce pas STARTTLS, le chiffrement est impossible (port 465 ? écrivez smtps://mail.example.net:465) SMTP mail.example.net:465 (TLS direct), étape « authentification » : le serveur a répondu « 535 5.7.8 authentication failed » — identifiant ou mot de passe refusé (dans l'URL, encodez @ en %40, : en %3A, / en %2F, % en %25) SMTP mail.example.net:465 (TLS direct) : connexion impossible (…) — hôte, port ou pare-feu
Le secret n'apparaît jamais dans un message d'erreur.
== Développement
[source,sh]
shards install crystal spec crystal tool format src spec
Les specs dialoguent avec un faux serveur SMTP local (spec/spec_helper.cr) ; elles n'envoient rien au dehors. Le shard est testé avec Crystal 1.21 et 1.22.
Le numéro de version est dans shard.yml seulement (lu par MartenMail::VERSION) ; chaque commit en relève le dernier chiffre.
marten-mail
- 0
- 0
- 0
- 1
- 1
- 22 days ago
- October 18, 2026
MIT License
Mon, 19 Oct 2026 07:23:23 GMT