Envoyer un lot
`emails.send_batch` : jusqu'à 100 messages, un résultat par élément.
emails.send_batch
invoices = [ {number: "INV-1042", email: "[email protected]"}, {number: "INV-1043", email: "[email protected]"}] messages = invoices.map do |invoice| {from: "[email protected]", to: invoice[:email], subject: "Invoice #{invoice[:number]}", text: "Your invoice is attached."}end result = client.emails.send_batch(messages, idempotency_key: "invoices:2026-09") puts "#{result.sent} sent, #{result.failed} failed" result.items.each do |item| if item[:status] == "error" warn "#{item[:index]} #{item.dig(:error, :code)} #{item.dig(:error, :message)}" else puts "#{item[:index]} #{item.dig(:email, :id)}" endendsend_batch prend un Array de Hashes de messages, chacun ayant exactement la forme du corps de emails.send, et renvoie un OpenEmail::BatchResult. Ses items contiennent un Hash par message, dans l'ordre, chacun soit ok avec son message, soit error avec l'enveloppe par laquelle ce message aurait été refusé. Rien n'est annulé : un compteur failed supérieur à 0 est donc une liste sur laquelle agir, pas une raison de renvoyer le batch.
Une seule clé d'idempotence couvre le batch et le serveur l'étend pour chaque élément : un batch réessayé rejoue donc chaque message au lieu de les confondre avec le premier. Renvoyez le même Array dans le même ordre quand vous le réessayez : un élément qui a changé de place est lié à la clé d'une autre position et revient sous forme d'erreur idempotency_key_reuse.
Un message refusé ne lève pas d'erreur. Seul un problème qui touche le batch dans son ensemble en lève une : un Array vide, plus de 100 messages, plus de 10 portant translate, un échec de clé ou de portée, ou une panne du serveur. Une panne du serveur en cours de route survient après le départ des éléments précédents, et le client réessaie avec la même clé, ce qui rejoue ces éléments au lieu de les envoyer deux fois.
Les éléments sont envoyés l'un après l'autre dans une seule requête : un gros batch d'envois immédiats prend donc nettement plus de temps qu'un seul send. Gardez un timeout: généreux sur le client.
Paramètres : emails.send_batch
emailsArray<Hash>obligatoire- De 1 à 100 messages, envoyés sous la forme `{"emails": [...]}` et acceptés un par un dans l'ordre donné. Chacun passe par le même traitement que `emails.send` : un destinataire seul est enveloppé, un Time devient un instant et les octets des pièces jointes sont encodés. Un Array vide, plus de 100 messages, ou plus de 10 messages portant `translate` font refuser tout l'appel avec une `validation_error` sur `emails`. Une portée `emails:send` manquante et une `idempotency_key:` mal formée font aussi refuser tout l'appel, avant l'envoi du moindre message.
idempotency_keyString- Déduplique le batch entre processus. Le client attache de toute façon une clé fraîchement générée à chaque appel, si bien que ses propres réessais n'envoient jamais deux fois, et le serveur étend la clé qu'il reçoit pour chaque élément en `key/0`, `key/1` et ainsi de suite, séparés par une barre oblique, un caractère que votre propre clé ne peut pas contenir : une seule clé portant sur cent messages ne peut donc pas les confondre avec le premier.
api_keyString- Envoie le batch avec cette clé au lieu de celle du client.
Chaque message de emails
fromString or Hashobligatoire- L'expéditeur, sous forme d'adresse nue, de `Name <addr@host>` ou d'un Hash avec `email` et `name`. Il n'y a pas d'expéditeur de repli et la clé doit être autorisée sur cette adresse. Un refus fait échouer ce seul élément, sous la forme d'un `permission_error` avec le code `from_address_forbidden`.
toString, Hash or Arrayobligatoire- Au moins un destinataire, et un destinataire seul est enveloppé dans un Array par le client. 50 adresses au maximum pour `to`, `cc` et `bcc` réunis, comptées par message et non sur l'ensemble du batch.
ccString, Hash or Array- Vide par défaut, et compte dans le même total de 50 adresses que `to` et `bcc`.
bccString, Hash or Array- Vide par défaut, et compte dans le même total de 50 adresses. `Bcc` fait partie des noms que `headers` ne peut pas définir : c'est donc le seul moyen de mettre en copie cachée. La forme en-tête annulerait l'enveloppe par destinataire qui garde l'adresse cachée.
replyToString or Hash- Où vont les réponses. Appliqué après `headers`, il écrase donc un `Reply-To` que vous y auriez également défini au lieu d'en ajouter un deuxième.
subjectString- 998 caractères au maximum, la limite de ligne de la RFC 5322, et une String vide par défaut. Un objet vide laisse la place à celui du modèle quand `template` en fournit un.
htmlString- La partie HTML, un million de caractères au maximum, et celle que voient les destinataires quand les deux corps sont fournis. `html`, `text`, `template` ou `draftId` : l'un des quatre est obligatoire, et un élément qui n'en a aucun échoue avec un `validation_error` sur `html`.
textString- La partie en texte brut, un million de caractères au maximum. Les deux peuvent être envoyées, et tout transport sur ce chemin construit un seul corps à partir d'une seule String : `html` l'emporte donc quand il y en a un.
headersHash- `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority et Feedback-ID uniquement. Tout ce que le transport définit lui-même (From, To, Bcc, Subject, Message-ID, les en-têtes DKIM et ARC) est refusé avec `reserved_header` plutôt qu'ignoré en silence. Les valeurs font au maximum 998 caractères et ne peuvent porter ni CR, ni LF, ni NUL, car une deuxième ligne est un deuxième en-tête.
attachmentsArray<Hash>- 20 fichiers au maximum par message, les fichiers inline totalisant 5 Mo une fois décodés, comptés par message et non par batch. `content` est en base64 sur le réseau. Passez les octets sous forme de String binaire, d'IO ou de Pathname, et le client les encode. Un Hash avec seulement `fileId` désigne un fichier déjà présent dans l'espace de travail et n'est pas compté dans le plafond inline.
threadIdString- Répond dans un thread existant, 256 caractères au maximum. Le transport en déduit In-Reply-To et References, ce qui fait atterrir la réponse dans la conversation plutôt qu'à côté.
draftIdString- Envoie le contenu d'un brouillon enregistré sous cette enveloppe, 256 caractères au maximum. Ce sont les destinataires, l'objet et les en-têtes construits ici qui partent sur le réseau.
templateHash- Rend un modèle stocké côté serveur, par id (`tpl_…`) ou par slug, `version` figeant une révision et `props` et `slots` le remplissant. Fixé une seule fois, au moment où l'élément est accepté, et refusé en présence de `html` ou `text` comme de `draftId`, puisque chacun d'eux est une deuxième réponse à la question du contenu du message.
scheduledAtTime, DateTime or String- Un Time ou un DateTime, un instant ISO 8601, ou une durée comme `PT1H`, au moins une seconde dans le futur et au plus 365 jours à l'avance. Une Date Ruby signifie minuit UTC ce jour-là. Les éléments se planifient indépendamment : un même batch peut donc contenir cent heures d'envoi différentes.
cancellableForSecondsInteger- Une fenêtre d'annulation en secondes sur un envoi immédiat, de 0 à 900, 0 par défaut. Toute valeur supérieure à 0 est refusée en présence de `scheduledAt` sur le même élément, puisqu'un message planifié est déjà annulable jusqu'à son départ.
trackingHash- `opens` et `clicks`, chacun optionnel et chacun surchargeant le réglage pour ce seul message. Une clé omise suit l'adresse depuis laquelle le message part (ou le catch-all qui l'a attrapée), dont le réglage est désactivé sauf si cette adresse l'a activé.
tagsHash- 10 libellés au maximum, avec des clés de 1 à 64 caractères pris dans `A-Za-z0-9_-` et des valeurs jusqu'à 256. Renvoyés tels quels sur le message et jamais interprétés : `emails.list` filtre sur `status:`, `from:`, `broadcast_id:` et la fenêtre de planification, et rien d'autre. Un tag est donc une chose à lire sur un message que vous détenez déjà, pas un moyen de le retrouver.
translateHash- Envoie cet élément dans une autre langue, fixé au moment de l'acceptation pour que les mots approuvés soient les mots qui partent. 10 éléments au maximum peuvent le porter dans un même batch : chacun consomme plusieurs appels au modèle et les éléments s'exécutent dans l'ordre, si bien qu'un batch plus grand serait interrompu en plein envoi. Au-delà, tout l'appel est refusé avec `too_many_items` sur `emails`, avant tout envoi.
Réponse : OpenEmail::BatchResult
itemsArray<Hash>- Un Hash par message, dans l'ordre où vous les avez envoyés. Rien n'est annulé : c'est donc le relevé de ce qui est arrivé à chaque message, pas le compte rendu d'une transaction. L'API répond 207 que tous les messages aient été acceptés, quelques-uns seulement ou aucun : l'appel retourne donc dans tous les cas, et c'est le `status` de chaque élément qui doit guider le code.
sentInteger- Combien d'éléments ont été ACCEPTÉS, ce qui n'est pas la même chose que combien sont partis. Un élément peut être `ok` et porter malgré tout un `email` dont le `status` vaut `failed` ou `partial`, car un transport qui refuse le message une fois la ligne créée est un résultat de distribution, pas une requête rejetée.
failedInteger- Combien d'éléments portent un `error`. Un compteur supérieur à 0 est une liste sur laquelle agir, pas une raison de renvoyer le batch. Les messages acceptés sont déjà partis.
Chaque élément
indexInteger- La position qu'occupait le message de cet élément dans l'Array que vous avez envoyé. Portée comme clé en plus de l'ordre, pour qu'un code qui filtre ou trie `items` puisse toujours dire quel message a échoué.
statusString- `ok` ou `error`. `ok` porte `email`, `error` porte `error`, et aucun élément ne porte les deux.
emailHash- Le message accepté, uniquement sur un élément `ok`, sous la même forme que celle que renvoie un envoi unique. Son `replayed` vaut true quand l'`Idempotency-Key` dérivée correspondait à un envoi qui existait déjà : rien de nouveau n'a alors été envoyé et c'est le message d'origine. Il ne porte pas de clé `tracking`, car l'engagement est signalé plus tard et il n'y a rien à signaler au moment de l'acceptation.
errorHash- Pourquoi ce message précis a été refusé, uniquement sur un élément `error`. C'est l'enveloppe d'erreur de l'API, moins `docUrl` et `requestId` : ceux-ci décrivent la requête, et la requête dans son ensemble a réussi.
L'erreur d'un élément
typeString- La catégorie sur laquelle se brancher : `validation_error`, `permission_error`, `not_found_error`, `conflict_error` et les autres. L'ensemble est figé et ne s'agrandira pas, contrairement à `code`.
codeString- L'échec précis : `from_address_forbidden`, `invalid_email_address`, `too_many_recipients`, `reserved_header`, `message_too_large`, `unknown_parameter`. Ouvert et additif : traitez donc un code que vous ne reconnaissez pas comme son `type`.
messageString- Une phrase écrite pour un humain, qui nomme la valeur fautive quand il y en a une. Ce n'est pas un identifiant stable. Branchez-vous sur `code`.
paramString- Le champ qui a été refusé, sous forme de chemin pointé à l'intérieur de CE message : `to.0`, `from`, `attachments`. Absent quand l'échec ne nomme aucun champ, et jamais préfixé par la position dans le batch, ce à quoi sert `index`.