Enviar un lote
`emails.send_batch`: hasta 100 mensajes, con resultados por elemento.
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 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`.