Enviar un correo
`emails.send`: un mensaje, ahora o más tarde.
emails.send
email = client.emails.send( from: {email: "[email protected]", name: "Acme Billing"}, to: ["[email protected]", "Grace <[email protected]>"], cc: "[email protected]", bcc: [{email: "[email protected]"}], replyTo: "[email protected]", subject: "Your September invoice", html: "<p>Invoice attached.</p>", text: "Invoice attached.", headers: {"X-Campaign" => "invoices"}, attachments: [{filename: "invoice.pdf", content: Pathname("invoice.pdf")}], threadId: "CAHk7pQ2x9LmZ4-mail.example.com", scheduledAt: "PT1H", tags: {order: "4021"}, tracking: {opens: true, clicks: true}) puts email[:id], email[:status]to, cc y bcc aceptan un destinatario o un Array de ellos, y si es uno solo se envuelve por ti. Cada uno puede ser una dirección escueta, Name <addr@host> o un Hash con email y name.
El mensaje se pasa como argumentos nombrados o como un solo Hash. Los argumentos nombrados junto a un Hash se fusionan con él y prevalecen cuando ambos nombran el mismo campo, así que client.emails.send(message, subject: "Re: your invoice") cambia un campo de un mensaje que construiste antes. Las claves conservan los nombres de la API, y por eso replyTo y scheduledAt siguen en camelCase, mientras que idempotency_key: y api_key: son opciones de la llamada y nunca forman parte del mensaje.
Parámetros
fromString or Hashobligatorio- El remitente. Una dirección escueta, `Name <addr@host>` o un Hash con `email` y `name`. Debe ser una con la que esta clave pueda enviar, o la llamada lanza un 403 `from_address_forbidden`. No hay remitente de reserva, así que un envío siempre nombra la dirección desde la que sale.
toString, Hash or Arrayobligatorio- Un destinatario o un Array de ellos, y si es uno solo se envuelve por ti. Como máximo 50 entre `to`, `cc` y `bcc` en conjunto, y más da un 422 `too_many_recipients`.
ccString, Hash or Array- Cuenta para el límite de 50 destinatarios.
bccString, Hash or Array- Nunca se nombra en los bytes que recibe nadie más, porque se transmite un sobre por destinatario. También cuenta para los 50.
replyToString or Hash- Una única dirección, enviada como la cabecera Reply-To.
subjectString- Como máximo 998 caracteres, el límite de línea de RFC 5322. Vacío por defecto, y un asunto vacío recurre al de la plantilla o al del borrador.
htmlString- Se requiere uno de `html`, `text`, `draftId` o `template`. El HTML es lo que ven los destinatarios cuando se dan tanto `html` como `text`. Como máximo 1.000.000 de caracteres.
textString- La parte de texto plano, como máximo 1.000.000 de caracteres.
templateHash- Renderiza una plantilla almacenada en el servidor: un Hash con `id`, que acepta un id o un slug, y `version` (un Integer), `props` y `slots` opcionales. `version` fija una revisión. Omítelo para usar lo que esté publicado cuando se acepte la solicitud. Una prop desconocida o ausente es un 422 y no un hueco en blanco en el mensaje.
draftIdString- Envía un borrador guardado con este sobre, tal como se escribió. No se puede combinar con `template` ni con `translate`.
headersHash- Nombre de cabecera a valor String, limitado a `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority y Feedback-ID. Todo lo que el transporte establece por sí mismo se rechaza con un 422 `reserved_header` en lugar de descartarse en silencio.
attachmentsArray<Hash>- Cada uno es un Hash con `filename`, `content` y un `contentType` opcional, o un Hash con solo `fileId`, que nombra un archivo que ya está en el espacio de trabajo, como uno de `files.upload`. Pasa bytes en `content` y se codifican en base64 por ti. 20 archivos, con los archivos en línea limitados a 5 MB en total una vez descodificados. Un archivo almacenado puede ser mayor y viaja como enlace de descarga.
attachmentDeliveryString- `mime`, `link` o `auto`. `auto` lleva los archivos como enlaces de descarga en cuanto superan 2 MB en un dominio con un dominio de archivos activo, y dentro del mensaje en caso contrario. Si se omite, se aplica la configuración del buzón, que por defecto es `auto`.
threadIdString- Responde dentro de un hilo existente. El transporte escribe In-Reply-To y References.
scheduledAtTime, DateTime or String- Un Time o un DateTime, enviado como instante ISO 8601 en UTC, un instante ISO 8601 como String, o una duración como `PT1H`. Hasta un año en el futuro, nunca en el pasado. No se puede combinar con `cancellableForSeconds`. Una Date de Ruby se envía como fecha sin hora, que la API lee como medianoche UTC de ese día, así que pasa un Time cuando la hora importe.
cancellableForSecondsInteger- De 0 a 900. Una ventana de deshacer en un envío inmediato: el mecanismo de deshacer del redactor, expuesto en lugar de fijado en el código.
tagsHash- Hasta 10 etiquetas, con claves de 1 a 64 letras, dígitos, `_` o `-` y valores String de hasta 256 caracteres. Se devuelven tal cual en cada lectura y nunca se interpretan.
signatureBoolean- Si este mensaje lleva la firma de la dirección desde la que se envía: la suya, si no la del catch-all para una dirección que captó un catch-all, y si no el pie de OpenEmail, salvo que esa dirección lo haya desactivado. Si se omite, un cuerpo `html` sale exactamente como está escrito, sin firma, y un cuerpo solo `text` la lleva. Pon `false` en el correo que un programa envía en nombre de alguien, como un recibo, un restablecimiento de contraseña o un resumen, ninguno de los cuales quiere la firma de una persona debajo. Los envíos con plantilla y los envíos cifrados nunca la llevan.
trackingHash- Un Hash con los Boolean opcionales `opens` y `clicks`: si añadir un píxel de apertura y reescribir los enlaces de este mensaje. Está desactivado salvo que se haya activado el seguimiento para la dirección desde la que se envía (o para el catch-all que la recogió), y cualquiera de las dos claves indicada aquí decide ese mensaje concreto sea cual sea la configuración de la dirección.
translateHash- Envíalo en el idioma del destinatario: un Hash con `to` y `from`, `subject` e `includeOriginal` opcionales. `to` acepta un código, un nombre en inglés o el nombre propio del idioma, y `subject` e `includeOriginal` tienen ambos true por defecto. Se resuelve cuando se acepta la solicitud, así que un mensaje programado lleva las palabras que se aprobaron. Se rechaza junto con `draftId`.
idempotency_keyString- Tu propia clave para este envío, de 1 a 255 caracteres entre letras, dígitos, `_`, `.`, `:` o `-`. Sin ella, el cliente genera una clave por llamada, así que sus propios reintentos nunca envían dos veces, y con ella un envío que se vuelve a ejecutar en otro proceso se reproduce en lugar de repetirse.
api_keyString- Envía con esta clave en lugar de la del cliente, para un proceso que envía en nombre de varios espacios de trabajo.
Respuesta
Un Hash con claves Symbol, así que email[:status] lee el estado.
idString- El id de envío, `msg_` seguido de 24 caracteres hexadecimales. Úsalo para `get`, `cancel`, `reschedule` y `get_tracking`.
statusString- queued, scheduled, sending, sent, partial, bounced, cancelled o failed. Lee esto en lugar del hecho de que la llamada haya devuelto algo: un envío inmediato se despacha dentro de la solicitud y suele volver como `sent`, `partial` o `failed`, y uno retenido vuelve como `queued` o `scheduled`. `partial` es un estado propio: algunos destinatarios ya lo tienen y no se les puede quitar el envío, así que reintentar es un error e informar de un fallo es mentir.
modeString- `live` o `test`: qué tipo de clave lo envió. Un envío de prueba se registra y nunca se transmite. Figura como `sent`, con `transport` igual a `test`, así que comprueba la respuesta y no un buzón.
fromString- La dirección realmente autorizada y puesta en la red, que no siempre es la que se pidió.
subjectString or nil- Tal como se envió.
messageIdString or nil- El Message-ID de RFC 5322. nil hasta que existe el MIME. El servicio de envío reescribe la cabecera a la salida, así que ningún rebote ni informe de entrega lleva este valor. `id` es aquello con lo que vuelve un evento.
threadIdString or nil- El hilo en el que aterrizó.
transportString or nil- Cómo salió el mensaje. nil hasta el despacho.
attemptsInteger- Cuántas veces se ha intentado el despacho.
lastErrorString or nil- Por qué falló el último intento, literalmente.
scheduledAtString or nil- El instante ISO 8601 en que está previsto que salga.
cancellableUntilString or nil- Mientras el momento actual sea anterior a este, `cancel` sigue funcionando.
sentAtString or nil- El instante ISO 8601 en que salió.
tagsHash- Lo que enviaste, devuelto tal cual.
sourceString- composer, api, mcp, ai o queue: qué superficie lo solicitó. `api` es este cliente.
createdAtString- El instante ISO 8601 en que se escribió el registro.
replayedBoolean- True cuando un Idempotency-Key coincidió con un envío que ya existía. No se envió nada nuevo, y este es el mensaje original tal como está ahora.
translationHash- Presente solo en un mensaje que se tradujo, y solo donde se lleva la solicitud almacenada completa: esta respuesta y `get`. Contiene `language`, `languageName`, `detectedSourceLanguage`, `subject` e `includeOriginal`, con códigos en lugar de filas de idioma completas. Una fila de lista nunca lo tiene, así que su ausencia allí no dice nada en ningún sentido.
En el idioma del destinatario
translate escribe el mensaje en el idioma de otra persona antes de que salga. El cuerpo, y el asunto salvo que lo desactives, se traducen cuando la API acepta la solicitud, y lo que salió de ahí es lo que sale: una traducción que no se pudo producir rechaza el envío en lugar de mandarlo en el idioma en que lo escribiste.
email = client.emails.send( from: "[email protected]", to: "[email protected]", subject: "Your September invoice", html: "<p>Invoice attached. Payment is due on the 14th.</p>", translate: {to: "de"}) p email[:translation]email[:translation] contiene entonces {language: "de", languageName: "German", detectedSourceLanguage: "en", subject: true, includeOriginal: true}.
Nadie leyó eso antes de que saliera. emails.translate es el mismo viaje de ida y vuelta detenido un paso antes. Muéstraselo a una persona, deja que lo cambie y luego envía lo que aprobó sin translate en la llamada. Pasarlo otra vez traduciría por segunda vez y descartaría sus ediciones.
preview = client.emails.translate( subject: "Your September invoice", html: "<p>Invoice attached. Payment is due on the 14th.</p>", to: "de") puts preview.dig(:language, :native), preview[:subject], preview[:html]print "Send it as it is? [y/N] " if $stdin.gets.to_s.strip.casecmp?("y") client.emails.send( from: "[email protected]", to: "[email protected]", subject: preview[:subject], html: preview[:html] )endp OpenEmail::LANGUAGES.size current = client.languages.listp current.size p OpenEmail.resolve_language("Deutsch")&.fetch(:code)p OpenEmail.resolve_language("zh-TW")&.fetch(:code)p OpenEmail.language_by_code("DE")&.fetch(:native)p OpenEmail.rtl_language?("ar")Esas líneas imprimen 200, las filas con las que viene esta versión, luego cuántas tiene la API ahora, y después "de", "zh-Hant", "Deutsch" y true. La tabla viene incluida, en el orden del selector, como OpenEmail::LANGUAGES, un Array congelado de Hashes con code, label, native, flag y rtl, así que se puede rellenar un selector antes de la primera solicitud. languages.list devuelve las mismas filas obtenidas de la red como un Array simple, para quien prefiera las actuales en vez de las que trajo esta versión. OpenEmail.resolve_language acepta un código, un nombre en inglés, un endónimo o un alias (zh-TW es un alias de un código que ya no se lista) y devuelve nil cuando nada coincide, OpenEmail.language_by_code busca un código exacto sin distinguir mayúsculas, y dieciséis de las filas son de derecha a izquierda. Busca en native, label y code a la vez, muestra native primero y almacena el código.
emails.translate no se reintenta automáticamente. Consume llamadas al modelo y no escribe nada, así que no hay nada que hacer idempotente y un reintento tras una solicitud sin respuesta solo pagaría la misma respuesta dos veces.
- Un idioma que la API no puede identificar es un
validation_errorentranslate.to, antes de enviar nada. translation_too_longpor encima de 30.000 caracteres,translation_not_configuredcuando la instalación no tiene IA configurada, un 429ai_quota_exceededcuando el espacio de trabajo ha usado las acciones de IA de hoy (se restablece a medianoche UTC y no se reintenta),translation_failedcuando el proveedor no respondió. Ninguno de ellos envía el mensaje sin traducir como alternativa.- Funciona con
template: lo que se traduce es la salida RENDERIZADA, así que un único cuerpo almacenado sirve para todos los idiomas en los que leen tus clientes. Una plantilla que renderiza un documento completo conserva su doctype, sus bloques<style>y sus reglas@font-face: solo el cuerpo va al modelo y el resto se vuelve a colocar alrededor. Su<title>se deja intacto, y de todos modos no lo muestra nada. - Un reintento no cuesta nada más. La traducción no forma parte de la huella de idempotencia (la solicitud sí, incluido
translate), así que reintentar un envío sin respuesta con el mismoIdempotency-Keyreproduce el mensaje que ya existe en lugar de traducir y enviar un segundo. - Un mensaje traducido que está en cola o programado conserva la redacción aprobada.
emails.reschedulesigue pudiendo moverlo, mientras queemails.updaterechaza una nueva redacción con un 409translation_locked, así que cambiar lo que dice implica cancelarlo y enviarlo de nuevo.
Adjuntos
content va en base64 por la red. Pasa los bytes y se codifican por ti: una String binaria como la que devuelve File.binread, un IO como un File abierto, o un Pathname, que se lee por ti.
attachments = [ {filename: "invoice.pdf", content: File.binread("invoice.pdf"), contentType: "application/pdf"}, {filename: "report.pdf", content: Pathname("report.pdf")}, {fileId: "file_6bb640f5b99e47deb758f1f5"}] client.emails.send( from: "[email protected]", to: "[email protected]", subject: "Your documents", text: "Both are attached.", attachments:)Una String marcada como texto, como la que devuelve File.read, se toma como si ya estuviera en base64, y una que no lo esté lanza ArgumentError antes de enviar nada. Lee los archivos con File.binread, o llama a .b sobre los bytes que llegaron marcados como texto.
OpenEmail.to_base64 está ahí si necesitas la misma codificación en otro sitio. Acepta una String binaria, un IO o un Pathname y devuelve base64 estricto, sin saltos de línea.