Ir a la documentación
Ruby

Enviar un lote

`emails.send_batch`: hasta 100 mensajes, con resultados por elemento.

emails.send_batch

send_batch.rb
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)}"  endend

send_batch acepta un Array de Hashes de mensaje, cada uno con exactamente la forma del cuerpo de emails.send, y devuelve un OpenEmail::BatchResult. Su items contiene un Hash por cada mensaje, en orden, cada uno ok con su mensaje o error con el sobre con el que se habría rechazado ese mensaje. Nada se revierte, así que un recuento failed mayor que 0 es una lista sobre la que actuar y no un motivo para reenviar el lote.

Una sola clave de idempotencia cubre el lote y el servidor la extiende para cada elemento, así que un lote reintentado reproduce cada mensaje en lugar de colapsarlos sobre el primero. Envía el mismo Array en el mismo orden cuando lo reintentes: un elemento que se movió queda ligado a la clave de otra posición y vuelve como un error idempotency_key_reuse.

Un mensaje rechazado no lanza nada. Solo lanza un problema con el lote en su conjunto: un Array vacío, más de 100 mensajes, más de 10 con translate, un fallo de clave o de ámbito, o un fallo del servidor. Un fallo del servidor a mitad de camino llega después de que hayan salido los elementos anteriores, y el cliente lo reintenta con la misma clave, que reproduce esos elementos en lugar de enviarlos dos veces.

Los elementos se envían uno tras otro dentro de una sola solicitud, así que un lote grande de envíos inmediatos tarda bastante más que un solo send. Mantén generoso el timeout: del cliente.

Parámetros: emails.send_batch

emailsArray<Hash>obligatorio
De 1 a 100 mensajes, enviados como `{"emails": [...]}` y aceptados de uno en uno en el orden dado. Cada uno pasa por el mismo tratamiento que `emails.send`, así que un destinatario solo se envuelve, un Time se convierte en un instante y los bytes de los adjuntos se codifican. Un Array vacío, más de 100, o más de 10 mensajes con `translate` rechazan la llamada entera con un `validation_error` en `emails`. La falta del ámbito `emails:send` y un `idempotency_key:` mal formado también rechazan la llamada entera, antes de enviar un solo mensaje.
idempotency_keyString
Deduplica el lote entre procesos. El cliente adjunta de todos modos una clave recién generada en cada llamada, así que sus propios reintentos nunca envían por duplicado, y el servidor extiende la clave que reciba por elemento como `key/0`, `key/1`, etc., separadas por una barra, un carácter que tu propia clave no puede contener, de modo que una sola clave para cien mensajes no puede colapsarlos sobre el primero.
api_keyString
Envía el lote con esta clave en lugar de la del cliente.

Cada mensaje de emails

fromString or Hashobligatorio
El remitente, como dirección escueta, `Name <addr@host>` o un Hash con `email` y `name`. No hay remitente de reserva y la clave debe tener permitida esta dirección. Un rechazo hace fallar únicamente ese elemento, como un `permission_error` con el código `from_address_forbidden`.
toString, Hash or Arrayobligatorio
Al menos un destinatario; si es uno solo, el cliente lo envuelve en un Array. Como máximo 50 direcciones entre `to`, `cc` y `bcc` en conjunto, contadas por mensaje y no en todo el lote.
ccString, Hash or Array
Por defecto ninguna, y cuenta para el mismo total de 50 direcciones que `to` y `bcc`.
bccString, Hash or Array
Por defecto ninguna, y cuenta para el mismo total de 50 direcciones. `Bcc` es uno de los nombres que `headers` no puede establecer, así que esta es la única forma de enviar copia oculta. La variante como cabecera desharía el sobre por destinatario que mantiene oculta la dirección.
replyToString or Hash
Adónde van las respuestas. Se aplica después de `headers`, así que sobrescribe un `Reply-To` que también hayas puesto ahí en lugar de añadir un segundo.
subjectString
Como máximo 998 caracteres, el límite de línea de RFC 5322, y por defecto una String vacía. Un asunto vacío cede el paso al de la plantilla cuando `template` proporciona uno.
htmlString
La parte HTML, de un millón de caracteres como máximo, y la parte que ven los destinatarios cuando se dan ambos cuerpos. Se requiere uno de `html`, `text`, `template` o `draftId`, y un elemento que no tenga ninguno falla como `validation_error` en `html`.
textString
La parte de texto plano, de un millón de caracteres como máximo. Pueden enviarse ambas, y todos los transportes de esta ruta construyen un solo cuerpo a partir de una sola String, así que `html` gana cuando lo hay.
headersHash
Solo `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority y Feedback-ID. Todo lo que el transporte establece por sí mismo (From, To, Bcc, Subject, Message-ID y las cabeceras DKIM y ARC) se rechaza como `reserved_header` en lugar de descartarse en silencio. Los valores tienen 998 caracteres como máximo y no pueden contener CR, LF ni NUL, porque una segunda línea es una segunda cabecera.
attachmentsArray<Hash>
Como máximo 20 archivos por mensaje, con los archivos en línea sumando 5 MB una vez descodificados, contados por mensaje y no por lote. `content` viaja en base64. Pasa los bytes como String binaria, IO o Pathname y el cliente los codifica. Un Hash con solo `fileId` nombra un archivo que ya está en el espacio de trabajo y no cuenta para el límite en línea.
threadIdString
Responde dentro de un hilo existente, con 256 caracteres como máximo. El transporte escribe In-Reply-To y References a partir de él, que es lo que hace que la respuesta aterrice en la conversación y no al lado.
draftIdString
Envía el contenido de un borrador guardado bajo este sobre, con 256 caracteres como máximo. Los destinatarios, el asunto y las cabeceras construidos aquí son los que van por la red.
templateHash
Renderiza una plantilla almacenada en el servidor, por id (`tpl_…`) o por slug, con `version` fijando una revisión y `props` y `slots` rellenándola. Se resuelve una sola vez, cuando se acepta el elemento, y se rechaza junto con `html` o `text` y junto con `draftId`, ya que cada uno de esos es una segunda respuesta a qué contiene el mensaje.
scheduledAtTime, DateTime or String
Un Time o un DateTime, un instante ISO 8601 o una duración como `PT1H`, al menos un segundo en el futuro y a lo sumo 365 días por delante. Una Date de Ruby significa medianoche UTC de ese día. Los elementos se programan de forma independiente, así que un lote puede contener cien horas de envío distintas.
cancellableForSecondsInteger
Una ventana de deshacer, en segundos, sobre un envío inmediato, de 0 a 900 y con 0 por defecto. Cualquier valor superior a 0 se rechaza junto con `scheduledAt` en el mismo elemento, ya que un mensaje programado ya se puede cancelar hasta que sale.
trackingHash
`opens` y `clicks`, cada uno opcional y cada uno sustituyendo la configuración solo para este mensaje. Una clave que omitas sigue a la dirección desde la que se envía el mensaje (o al catch-all que la recogió), que está desactivado salvo que esa dirección lo haya activado.
tagsHash
Como máximo 10 etiquetas, con claves de 1 a 64 caracteres tomados de `A-Za-z0-9_-` y valores de hasta 256. Se devuelven tal cual en el mensaje y nunca se interpretan: `emails.list` filtra por `status:`, `from:`, `broadcast_id:` y la ventana de programación y nada más, así que una etiqueta es algo que se lee de un mensaje que ya tienes y no una forma de encontrarlo.
translateHash
Envía este elemento en otro idioma, resuelto en el momento de la aceptación para que las palabras aprobadas sean las que salen. Como máximo 10 elementos de un lote pueden llevarlo: cada uno consume varias llamadas al modelo y los elementos se ejecutan en orden, así que un lote mayor se interrumpiría a mitad de envío. Por encima de eso, la llamada entera se rechaza como `too_many_items` en `emails`, antes de enviar nada.

Respuesta: OpenEmail::BatchResult

itemsArray<Hash>
Un Hash por cada mensaje, en el orden en que los enviaste. Nada se revierte, así que esto es un registro de lo que pasó con cada mensaje y no un informe sobre una transacción. La API responde 207 tanto si se aceptaron todos los mensajes como si solo algunos o ninguno, así que la llamada devuelve en cualquier caso y el `status` de cada elemento es lo que hay que evaluar.
sentInteger
Cuántos elementos se ACEPTARON, que no es lo mismo que cuántos salieron. Un elemento puede ser `ok` y aun así llevar un `email` cuyo `status` es `failed` o `partial`, porque un transporte que rechaza el mensaje después de que exista la fila es un resultado de entrega y no una solicitud rechazada.
failedInteger
Cuántos elementos llevan un `error`. Un recuento mayor que 0 es una lista sobre la que actuar y no un motivo para reenviar el lote. Los mensajes aceptados ya han salido.

Cada elemento

indexInteger
La posición que ocupaba el mensaje de este elemento en el Array que enviaste. Se incluye como clave además de como orden, para que el código que filtra u ordena `items` pueda seguir diciendo qué mensaje falló.
statusString
`ok` o `error`. `ok` lleva `email`, `error` lleva `error`, y ningún elemento lleva ambos.
emailHash
El mensaje aceptado, solo en un elemento `ok`, con la misma forma que devuelve un envío individual. Su `replayed` es true cuando el `Idempotency-Key` derivado coincidió con un envío que ya existía, así que no se envió nada nuevo y este es el mensaje original. No lleva la clave `tracking`, porque la interacción se informa más tarde y en el momento de la aceptación no hay nada que informar.
errorHash
Por qué se rechazó este mensaje concreto, solo en un elemento `error`. Es el sobre de error de la API menos `docUrl` y `requestId`: esos describen la solicitud, y la solicitud en su conjunto tuvo éxito.

El error de un elemento

typeString
La categoría por la que ramificar: `validation_error`, `permission_error`, `not_found_error`, `conflict_error` y las demás. El conjunto está congelado y no crecerá, a diferencia de `code`.
codeString
El fallo concreto: `from_address_forbidden`, `invalid_email_address`, `too_many_recipients`, `reserved_header`, `message_too_large`, `unknown_parameter`. Es abierto y aditivo, así que trata un código que no reconozcas según su `type`.
messageString
Una frase escrita para una persona, que nombra el valor problemático cuando lo hay. No es un identificador estable. Usa `code` para ramificar.
paramString
El campo que se rechazó, como ruta con puntos dentro de ESE mensaje: `to.0`, `from`, `attachments`. Ausente cuando el fallo no nombra ningún campo, y nunca lleva como prefijo la posición en el lote, para lo cual está `index`.