marten-mail

Envoi des courriels par SMTP pour Marten, sans dépendance (client SMTP, messages MIME, backend Marten::Emailing)

= 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 des Marten::Email d'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.

Repository

marten-mail

Owner
Statistic
  • 0
  • 0
  • 0
  • 1
  • 1
  • 22 days ago
  • October 18, 2026
License

MIT License

Links
Synced at

Mon, 19 Oct 2026 07:23:23 GMT

Languages