ENbext.dev
DocumentationFonctionnalités

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Courrier

La fonctionnalité Envoi de courriels répond à une question : , comment puis-je envoyer un e-mail sur le réseau, sans me soucier de savoir si le backend est un relais SMTP ou une API cloud ? Un MailerPlugin est un transport : remettez-lui un message entièrement formé, récupérez un identifiant opaque et continuez. Le trait est délibérément neutre vis-à-vis du fournisseur : la même forme couvre un client SMTP soutenu par une lettre parlant à votre propre suffixe et un client aws-sdk-sesv2 parlant à Amazon SES. Ce qu'il fait, et non, c'est le rendu des modèles - c'est le travail du futur Capacité de modèle, et la division maintient le courrier petit, ennuyeux et facile à échanger.

Cette page couvre la surface des traits, le contrat corps d'entrée/sortie, comment les erreurs sont classées afin que les appelants sachent si une nouvelle tentative est sûre et comment les deux plugins de référence — @bext/mailer-smtp et @bext/mailer-ses — exercent la forme.

Le trait#

Chaque plugin Mailer implémente un trait, MailerPlugin, défini dans bext-plugin-api::mailer :

rust
pub trait MailerPlugin: Send + Sync {
    fn name(&self) -> &str;

    fn send(&self, msg: Message) -> Result<SendOutcome, MailerError>;

    fn send_batch(&self, msgs: Vec<Message>) -> Vec<BatchOutcome> {
        msgs.into_iter().map(|m| self.send(m)).collect()
    }

    fn is_healthy(&self) -> bool { true }
}

Chaque méthode est synchrone (correspondant à tous les autres traits de bext-plugin-api), de sorte que la forme se déplace proprement à travers les bacs à sable WASM, QuickJS et nsjail dans lesquels les plugins peuvent s'exécuter. Les backends qui nécessitent des E/S asynchrones (le SDK AWS SES, par exemple) bloquent sur leur propre handle d'exécution à l'intérieur du corps du trait. Le plugin de référence SES montre le modèle : soit il emprunte un tokio Handle à l'hôte, soit possède son propre Runtime, puis appelle handle.block_on(req.send()) depuis send.

Message, Address, Attachment, SendOutcome#

Les types de données pris en charge sont intentionnellement petits et conformes à la RFC 5322 :

rust
pub struct Address {
    pub email: String,
    pub name: Option<String>,
}

pub struct Attachment {
    pub filename: String,
    pub content_type: String,
    pub bytes: Vec<u8>,
    pub content_id: Option<String>,
}

pub struct Message {
    pub from: Address,
    pub to: Vec<Address>,
    pub cc: Vec<Address>,
    pub bcc: Vec<Address>,
    pub reply_to: Vec<Address>,
    pub return_path: Option<String>,
    pub subject: String,
    pub text: Option<String>,
    pub html: Option<String>,
    pub attachments: Vec<Attachment>,
    pub headers: Vec<(String, String)>,
}

pub struct SendOutcome {
    pub id: String,
}

Address est la boîte aux lettres RFC 5322 : un e-mail et un nom d'affichage facultatif. Attachment contient des octets bruts et un Content-ID facultatif pour les images en ligne référencées à partir de HTML via cid:.... Le Message possède tout ce dont un transport a besoin, y compris un return_path en option pour le routage DSN afin que le From: et l'expéditeur de l'enveloppe puissent diverger.

SendOutcome::id est une chaîne opaque attribuée par le backend : un Message-ID SMTP pour l’extension SMTP, ou un MessageId SES pour l’extension SES. Les appelants la traitent comme un identifiant de corrélation pour les journaux, les webhooks et les clés d’idempotence. Le trait ne garantit aucun format, et l’extension SMTP peut la laisser vide si le transport ne fournit pas d’identifiant.

Corps déjà rendus#

Attention

Au moins un des champs text ou html doit être renseigné pour chaque Message. Si les deux sont vides, le backend renvoie MailerError::InvalidMessage sans tenter l’envoi ; cette erreur ne doit pas être réessayée.

Le type Message contient text: Option<String> et html: Option<String>. Au moins l’un des deux DOIT être renseigné : un backend recevant les deux vides renvoie MailerError::InvalidMessage sans tenter l’envoi. Il ne contient aucune notion de nom de modèle, de variables ou de mise en page. C’est volontaire : les modèles relèvent d’une fonctionnalité distincte, qui pourra être fournie par Template. Les mêler à Mailer imposerait un moteur inutile à chaque extension de transport. Les appelants peuvent aujourd’hui effectuer le rendu avec MJML, MRML, Handlebars ou un composant JSX, puis transmettre les chaînes finales au service d’envoi.

Pourquoi la modification des en-têtes peut être refusée#

Message::headers est un Vec<(String, String)> pour les en-têtes supplémentaires comme List-Unsubscribe, List-Unsubscribe-Post, In-Reply-To ou References. Les backends PEUVENT rejeter les en-têtes qu'ils possèdent eux-mêmes — Bcc, Date, Message-ID, From, To, Subject — en renvoyant MailerError::InvalidMessage. Le plugin de référence SMTP fait exactement cela, rejetant toute tentative de définition de Message-ID car lettre attribue le sien.

Classement des erreurs#

Chaque échec passe par une énumération :

rust
pub enum MailerError {
    InvalidMessage(String),
    Unauthorized(String),
    Rejected(String),
    Transport(String),
    RateLimited(String),
    Unknown(String),
}

La variante est la chose sur laquelle les appelants se branchent. Le String interne est un détail fourni par le backend pour les journaux – ne l’analysez pas.

Variante Signification Réessayer ?
InvalidMessage Le message est mal formé : destinataire manquant, corps vide, en-tête incorrect. Non, corrigez le message.
Unauthorized Expéditeur non vérifié, SMTP AUTH refusé, identité SES manquante. Non - corrigez la configuration.
Rejected Le backend a accepté la demande puis refusé : liste de suppression, spam. Non.
Transport Problème au niveau du réseau : réinitialisation TCP, DNS, réponse 5xx. Oui, avec recul.
RateLimited Le backend vous a demandé de ralentir : SMTP 421, limitation SES. Oui, après un certain temps.
Unknown Le backend n'a pas pu classer. Traitez comme Transport. Avec prudence.

Les deux plugins de référence partagent le même esprit de classification mais utilisent des signaux différents. Le plugin SMTP a un chemin classify_smtp qui inspecte la catégorie Error::status().severity plus saisie par la lettre et revient à un classify_generic basé sur un mot-clé pour les erreurs de transport. Le plugin SES mappe directement les variantes typées SendEmailError : MessageRejected → Rejected, LimitExceededException → RateLimited, BadRequestException → InvalidMessage.

Lot comme opération de première classe#

send_batch est distinct car la différence de coût est importante. L'impl par défaut boucle sur send, ce qui est la bonne chose pour SMTP (un message par transaction SMTP est normal). Le plugin de référence SES remplace send_batch pour ventiler les envois en parallèle sur son runtime tokio plutôt qu'en série. Il fait que et non utilisent le SendBulkEmail natif de SES, car ce chemin nécessite un modèle SES prédéfini (une préoccupation qui appartient à la future capacité Template) plutôt que les corps pré-rendus que ce trait comporte.

Le vecteur de retour est un résultat par entrée dans le même ordre. Les appelants corrèlent les échecs partiels par index. Un échec au niveau du transport qui abandonne l'ensemble du lot se reflète dans chaque entrée portant le même type d'erreur.

Les plugins de référence#

@bext/mailer-smtp#

Vit à crates/bext-impls/bext-mailer-smtp. Construit sur lettre 0.11 avec les fonctionnalités builder, smtp-transport, rustls-tls, ring et hostname — aucune dépendance OpenSSL. Le plugin est générique sur le trait Transport : le code de production utilise SmtpTransport::relay(host) (TLS implicite sur le port 465) et les tests utilisent StubTransport donc rien ne touche le fil.

Les éléments non évidents :

  • Gestion des enveloppes. Lorsque return_path est défini, le plugin pré-construit un lettre::address::Envelope::new(Some(parsed), recipients) et l'attache au constructeur avant .multipart(body). lettre n'expose pas de méthode de création return_path et vous ne pouvez pas muter l'enveloppe une fois le message généré.
  • En-têtes bruts. Les en-têtes personnalisés atterrissent via headers.insert_raw(HeaderValue::new(...)) avec HeaderName::new_from_ascii. Le plugin refuse de définir les en-têtes que lettre possède déjà.
  • Carrosseries alternatives. text + html produisent ensemble un multipart/alternative ; text ou html produit à lui seul un seul SinglePart. Les pièces jointes enveloppent le corps dans multipart/mixed.
  • Accessoires en ligne. Lorsque content_id est défini, le plugin utilise lettre::message::Attachment::new_inline(cid) pour que HTML <img src="cid:..."> fonctionne.

@bext/mailer-ses#

Vit à crates/bext-impls/bext-mailer-ses. Construit sur aws-sdk-sesv2 + aws-config. Le plugin encapsule un aws_sdk_sesv2::Client plus un runtime tokio : construisez-le avec with_client_and_handle(client, handle) lorsque l'hôte exécute déjà un runtime, ou with_client_owned_runtime(client) dans le cas contraire.

Les éléments non évidents :

  • Routage simple ou brut. SES possède un chemin de contenu Simple (corps structuré + en-têtes) et un chemin Raw (blob MIME construit à la main). Le plugin utilise Simple pour les messages simples – SES gère l'encodage, les en-têtes et les métadonnées de livraison. Il passe à Raw lorsque le message contient des pièces jointes en-têtes supplémentaires ou, et construit lui-même le MIME en plusieurs parties (y compris un encodeur base64 en ligne, pour éviter de tirer dans une caisse).
  • Jeu de configurations. Les appelants peuvent joindre un nom d'ensemble de configuration SES au moment de la construction ; c'est ainsi que vous acheminez les envois via un ensemble de réputation spécifique, une destination d'événement ou un pool IP dédié.
  • Test . Les tests utilisent aws_smithy_mocks avec la fonctionnalité test-util de aws-sdk-sesv2 activée. Les règles sont créées avec mock!(Client::send_email).then_output(...) ou .then_error(...) et transmises à mock_client!(aws_sdk_sesv2, &[&rule]) — le test par lots utilise .repeatedly() afin que la règle ne s'épuise pas.

Configuration#

Les deux plugins de référence lisent leur configuration depuis bext.config.toml :

toml
[mailer]
plugin = "@bext/mailer-smtp"

[mailer.smtp]
relay       = "smtp.example.com"
username    = "apikey"
password    = "{{env.SMTP_PASSWORD}}"
starttls    = true
timeout_ms  = 10_000

Pour le SSE :

toml
[mailer]
plugin = "@bext/mailer-ses"

[mailer.ses]
region            = "eu-west-1"
configuration_set = "transactional"

Les informations d'identification pour le plug-in SES proviennent de la chaîne d'informations d'identification AWS standard (variables d'environnement, IMDS, identité de pod EKS, profils) chargée via aws_config::load_defaults(BehaviorVersion::latest()). Le plugin ne lit ni ne stocke les clés lui-même.

Hors de portée#

  • Modèle . Corps pré-rendus uniquement. Une future capacité Template se superposera.
  • Carnet d’adresses/listes de destinataires. Les appelants gèrent les leurs.
  • Gestion des rebonds. SES envoie des rebonds à un sujet SNS ; le plugin ne s'abonne pas.
  • Webhooks de livraison. Faites correspondre le SendOutcome::id opaque avec le flux d'événements publié par votre backend.

Le trait est un transport. Tout est plus riche en couches au-dessus.

En rapport#

  • Aperçu des capacités — l'échelle de promotion et pourquoi les cinq fondamentaux sont expédiés ensemble.
  • Capacité planifiée — lancez un mailer.send(...) selon un planning (rapport quotidien, e-mails de synthèse).
  • Capacité de traceur — corrèle le SendOutcome::id d'un envoi avec une durée de requête sortante.
  • Intégration WordPress — comment bext-wp relie wp_mail() à la fonctionnalité Mailer via le SDK.
  • Construire un plugin — échafaudez un plugin Mailer et déclarez provides_capabilities = ["mailer"].
Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.