E-mail

sdk.email.send envoie un e-mail depuis le code serveur de votre application : confirmation de commande, notification de formulaire, lien d'invitation. L'envoi passe par le serveur SMTP configuré pour votre application, et un appel correspond à un message.

Pour les newsletters et les envois à une liste de contacts, utilisez plutôt les campagnes décrites dans Email & MJML.

API

import { createSdk } from "@bext-stack/framework/sdk";

const sdk = createSdk("<app-id>");

await sdk.email.send({
  to: ["client@example.com"],
  subject: "Votre commande est confirmée",
  html: "<p>Merci pour votre commande.</p>",
  text: "Merci pour votre commande.",
});

Appelez-la côté serveur (loader, action, route.ts ou tâche), jamais depuis un island.

Champ Type Description
to string[] Destinataires, au moins un. Une adresse simple (a@b.com) ou avec un nom (Jeanne Martin <a@b.com>).
subject string Objet du message.
html string? Corps HTML.
text string? Corps en texte brut.

Il faut au moins html ou text. Avec les deux, le message part en version HTML avec une alternative texte, et le client de messagerie choisit celle qu'il affiche.

send retourne true quand le serveur SMTP a accepté le message. En cas d'échec, l'appel lève une Error qui porte le message d'erreur renvoyé par la plateforme.

Expéditeur et adresse de réponse

L'adresse d'expéditeur (From) ne fait pas partie du message : send n'a pas de champ from. Elle vient de la configuration d'envoi de votre application, qui réunit le serveur SMTP, ses identifiants, l'adresse et le nom d'expéditeur, et une adresse de réponse par défaut. Le client du framework n'a pas d'appel pour lire ou modifier cette configuration. Une application sans configuration d'envoi ne peut pas envoyer : chaque appel lève une erreur.

Si la configuration prévoit une adresse de réponse, elle s'applique à tous les messages. Pour la remplacer sur un message (le formulaire de contact où la réponse doit aller au visiteur), ajoutez le champ reply_to, présenté plus bas.

Champs supplémentaires : reply_to et attachments

Le client transmet l'objet du message tel quel, et la plateforme accepte deux champs que le type BextSdk ne déclare pas :

Champ Type Description
reply_to string Adresse de réponse pour ce message. Remplace celle de la configuration.
attachments tableau Pièces jointes, chacune sous la forme { filename, content_type, content_base64 }.

Pour chaque pièce jointe :

  • content_base64 contient le fichier encodé en base64. Une pièce jointe sans ce champ est ignorée sans erreur ; un contenu qui n'est pas du base64 valide fait échouer l'envoi.
  • filename vaut attachment par défaut.
  • content_type vaut application/octet-stream par défaut. Un type MIME mal formé fait échouer l'envoi.

Comme ces champs ne figurent pas dans le type, construisez le message dans une variable avant de le passer à send : TypeScript refuserait un champ inconnu écrit directement dans l'appel.

const msg = {
  to: ["compta@example.com"],
  subject: "Facture F-2026-0142",
  text: "Vous trouverez la facture en pièce jointe.",
  reply_to: "facturation@example.com",
  attachments: [
    {
      filename: "F-2026-0142.pdf",
      content_type: "application/pdf",
      content_base64: pdfBase64,
    },
  ],
};
await sdk.email.send(msg);

Le base64 grossit le fichier d'environ un tiers, et tout le message voyage dans une seule requête : gardez les pièces jointes légères, et envoyez plutôt un lien pour un fichier volumineux.

Exemple : formulaire de contact

// src/app/api/contact/route.ts
import { json } from "@bext-stack/framework";
import { createSdk } from "@bext-stack/framework/sdk";

const sdk = createSdk("<app-id>");

const esc = (s: string) =>
  s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");

export async function POST(request: Request) {
  const { name, email, message } = await request.json();
  const msg = {
    to: ["contact@example.com"],
    subject: `Demande de contact : ${name}`,
    text: `${name} <${email}>\n\n${message}`,
    html: `<p><strong>${esc(name)}</strong> (${esc(email)})</p><p>${esc(message)}</p>`,
    reply_to: email,
  };
  try {
    await sdk.email.send(msg);
    return json({ ok: true });
  } catch (e) {
    return json({ ok: false, error: String(e) }, 502);
  }
}

reply_to permet de répondre directement au visiteur depuis la boîte de réception, alors que le message part de l'adresse d'expéditeur de l'application.

Erreurs

Cause Nature
to vide ou absent définitive
Adresse de destinataire ou reply_to invalide définitive
Ni html ni text définitive
Pièce jointe au base64 invalide ou au type MIME mal formé définitive
Aucune configuration d'envoi pour l'application définitive
Refus définitif du serveur SMTP (code 5xx, identifiants refusés par exemple) définitive
Serveur SMTP injoignable, ou refus temporaire (code 4xx) temporaire

Une erreur définitive se reproduira à l'identique : corrigez le message avant de réessayer. Une erreur temporaire peut passer au prochain essai. Le message de l'Error contient le détail renvoyé par la plateforme ou le serveur SMTP.

L'appel est aussi borné par le timeoutMs du client (30 secondes par défaut). Un délai dépassé lève une erreur, mais ne prouve pas que le message n'est pas parti.

À savoir

  • Un seul essai : send tente l'envoi une fois, sans file ni nouvelle tentative. Pour réessayer automatiquement, envoyez depuis un handler de Queue qui répond 500 en cas d'échec. Une erreur définitive y sera retentée inutilement jusqu'à la dead-letter : gardez le try/catch et répondez 200 quand le message lui-même est en cause.
  • Tous les destinataires sont visibles : chaque adresse de to figure dans l'en-tête To, et chaque destinataire voit les autres. Il n'y a ni copie (cc) ni copie cachée (bcc). Pour des envois individuels, appelez send une fois par destinataire.
  • Le HTML part tel quel : rien n'est échappé ni nettoyé. Échappez les valeurs saisies par vos visiteurs avant de les insérer dans html, comme dans l'exemple.
  • Pas de suivi : send ne retourne que true. Il n'y a ni identifiant de message exposé par le client, ni statut de remise, d'ouverture ou de rebond.

Et ensuite

Page Ce qu'elle couvre
Queue Envoyer depuis un handler, avec nouvelles tentatives et dead-letter
Email & MJML Newsletters, campagnes et statistiques d'envoi
Secrets Valeurs et configuration propres à l'application
Présentation du SDK Le client createSdk et l'interface BextSdk