Sign inGet Started

Enviar correo electrónico

POST /v1/email/messages envía un correo electrónico. Proporciona un remitente, destinatarios y contenido en un payload JSON. El API devuelve 202 Accepted con un ID de mensaje y luego entrega el correo de forma asíncrona. Consulta la referencia de API para ver los esquemas completos.

Un envío mínimo

El payload válido más pequeño es un from, al menos un destinatario to, un subject y un cuerpo (html, text o ambos). La dirección from debe estar en un dominio que hayas verificado en este espacio de trabajo, o en el dominio de onboarding.
const msg = await bird.email.send({
  from: { email: "onboarding@messagebird.dev", name: "Bird" },
  to: ["delivered@messagebird.dev"],
  subject: "Hello from Bird",
  html: "<p>My first Bird email.</p>",
});
console.log(msg.id, msg.status); // "em_…", "accepted"
Usa tu host regional (https://us1.platform.bird.com o https://eu1.platform.bird.com) con una clave bk_{region}_... correspondiente.
El ejemplo de envío usa delivered@messagebird.dev, una dirección de sandbox que siempre acepta correo. El API rechaza dominios de marcador de posición con un 422: example.com, example.net, example.org, example.edu, test.com, y cualquier dominio bajo los TLD reservados .test, .example, .invalid o .localhost. Un envío a estos dominios solo puede rebotar, lo que perjudica tu reputación de remitente.

Enviar antes de verificar un dominio

Durante el onboarding puedes enviar desde nuestro dominio compartido de onboarding, onboarding@messagebird.dev. Esos envíos omiten la verificación de dominio, pero solo llegan a miembros verificados de tu propio espacio de trabajo y a direcciones de sandbox, bajo un límite diario de destinatarios. El quickstart tiene las reglas y límites exactos.

Construir el payload

Destinatarios

to, cc y bcc aceptan hasta 50 direcciones cada uno, y to necesita al menos una. Cada entrada es una cadena de correo electrónico simple, una cadena de buzón RFC 5322 (Jane <jane@acme.com>) o un objeto con un nombre para mostrar opcional.
Los destinatarios en la lista de supresión del espacio de trabajo no hacen fallar la solicitud. Sigue devolviendo un 202, y cada destinatario suprimido aparece en los endpoints de lectura como status: rejected con el motivo recipient_suppressed, incluso cuando todos los destinatarios del envío están suprimidos.

Contenido

subject es obligatorio para envíos inline, hasta 998 caracteres. Proporciona html, text o ambos, cada uno hasta 524.288 caracteres. Envía ambos siempre que puedas: un cliente que no puede renderizar HTML recurre a la parte de texto.
Para personalizar contenido inline, coloca tokens {{ variable }} en el asunto o el cuerpo y pasa sus valores en parameters, hasta 16 KB serializados. Un único conjunto de valores cubre a todos los destinatarios del envío, y un token sin clave coincidente se renderiza vacío. Para contenido que reutilizas, envía una plantilla en su lugar.
Incluye parameters, incluso como objeto vacío ({}), para procesar el asunto y el cuerpo como Liquid. Omítelo para enviar tokens como {{ animal }} tal cual. Cada nombre de parámetro es una sola palabra, como first_name; los nombres con puntos y el nombre reservado bird se rechazan. La sintaxis Liquid inválida y los tags o filtros no soportados devuelven 422.
Los valores insertados en HTML se escapan para que no puedan alterar el marcado circundante. Para un enlace completo o una URL de imagen, usa {{ link }} sin url_encode. Para un valor dentro de una query de URL, codifica ese valor explícitamente, por ejemplo https://example.com/search?q={{ query | url_encode }}.

Reply-to y encabezados personalizados

reply_to acepta de 1 a 25 direcciones, en los mismos formatos que los destinatarios. Todas las respuestas de los destinatarios llegan a todas ellas, así que una o dos es lo habitual.
headers es un objeto de cadena a cadena para tus propios encabezados, por ejemplo {"X-Campaign": "spring-2026"}, con un máximo de 25 encabezados y valores de hasta 998 caracteres. Tres tipos de encabezado se devuelven como un 422:
  • Encabezados de direccionamiento y plataforma. Configura el direccionamiento del mensaje a través de los campos dedicados (from, to, cc, bcc, reply_to, subject). Esos nombres, y los encabezados que generamos por ti (Content-Type, Content-Transfer-Encoding, DKIM-Signature, Received, Return-Path), no se pueden establecer aquí.
  • List-Unsubscribe y List-Unsubscribe-Post en un envío marketing. Nosotros establecemos un encabezado de cancelación de suscripción con un clic conforme en esos envíos. En un envío transactional dejamos los tuyos exactamente como los configuraste.
  • Cualquier valor con un retorno de carro o salto de línea.

Seguimiento

track_opens y track_clicks tienen como valor predeterminado true. Establece cualquiera de los dos en false para omitir la inyección del píxel de apertura o la reescritura de enlaces en este envío. Seguimiento y métricas cubre lo que cada uno cambia en el mensaje.

Categoría y pool de IP

category clasifica el contenido y establece la política de supresión: marketing bloquea la entrega por cualquier motivo de supresión y por cualquier opt-out, y transactional entrega a pesar de una supresión por queja o un opt-out solo de marketing (uno registrado para todos los mensajes también lo bloquea). Su valor predeterminado es la categoría de la plantilla en un envío con plantilla y marketing en caso contrario, así que establece transactional explícitamente para recibos, restablecimientos de contraseña y otro correo operacional. Categorías cubre la elección. El correo enviado por SMTP toma su categoría de la configuración SMTP de la clave.
ip_pool_id selecciona el pool de envío: un ID de pool (ipp_...), o ipp_shared para enrutar por el pool compartido explícitamente. Omítelo para usar el pool predeterminado de tu organización. Un pool desconocido, o uno sin IP dedicadas disponibles para enviar, se rechaza con un 422.

Referencia de campos

CampoTipoObligatorioLímites y notas
fromaddresssíDebe estar en un dominio verificado o en el dominio de onboarding
toaddress[]sí1 a 50
cc, bccaddress[]noHasta 50 cada uno
subjectstringenvíos inlineHasta 998 caracteres; omitir en envíos con plantilla
html, textstringal menos unoHasta 524.288 caracteres cada uno; omitir en envíos con plantilla
reply_toaddress[]no1 a 25; las respuestas llegan a todas las direcciones listadas
headersobject (string → string)noHasta 25; nombres reservados rechazados (ver encabezados personalizados)
parametersobjectnoValores para {{ tokens }} en contenido inline; hasta 16 KB serializados; compartidos entre destinatarios
tags{name, value}[]noHasta 20; nombre ≤ 32 chars, valor ≤ 64 chars; solo [A-Za-z0-9_-]; nombres únicos por envío
metadataobjectnoJSON arbitrario, hasta 2 KB serializados
track_opensbooleannoPredeterminado true
track_clicksbooleannoPredeterminado true
categorystringnomarketing o transactional; predeterminado a la de la plantilla en un envío con plantilla, de lo contrario marketing
ip_pool_idstringnoipp_... o ipp_shared; omitir para el pool predeterminado de tu organización
templateobjectnoEnvía una plantilla publicada por id o slug, con parameters para sus variables y un language opcional
attachmentsobject[]noHasta 20; ver adjuntos
scheduled_atRFC 3339 timestampnoPrograma contenido en línea o una template; consulta envío programado

Enviar con una plantilla

En lugar de contenido inline, envía una plantilla publicada: establece template como un objeto que la nombre por id (emt_...) o por slug, exactamente uno de los dos, con los valores de sus variables en template.parameters. Omite subject, html y text, porque la plantilla ya los tiene.
const msg = await bird.email.send({
  from: { email: "onboarding@messagebird.dev", name: "Bird" },
  to: ["delivered@messagebird.dev"],
  category: "transactional",
  template: {
    slug: "welcome-email",
    parameters: { first_name: "Jane" },
  },
});
console.log(msg.id, msg.status);
El contenido de una plantilla es Liquid, así que además de la sustitución simple {{ variable }} puede usar filtros, condicionales {% if %} y bucles {% for %}. Personalizar con variables lista los pocos constructos que una publicación rechaza. template.parameters es donde colocas los valores para los parámetros propios de la plantilla, identificados por nombre. Si omites uno, el envío se rechaza con un 422 que lo nombra. Todo lo demás del envío se comporta igual que inline, incluyendo destinatarios, tags, metadata, seguimiento y adjuntos. Lo que es específico de un envío con plantilla:
  • Inline o con plantilla, nunca ambos. Enviar template junto con subject, html o text se rechaza con un 422. El API también rechaza valores de variables en el campo de nivel superior parameters; en un envío con plantilla pertenecen a template.parameters.
  • bird es el único nombre reservado. Una ruta de marcador de posición que comienza con bird. nombra nuestros propios datos, como el enlace de cancelación de suscripción o el registro de contacto del destinatario, por lo que una clave template.parameters no puede llamarse bird. Todas las demás claves las defines tú, y cada una es una sola palabra: {"order_number": "A-1043"} rellena {{ order_number }}.
  • Una plantilla se puede enviar ahora o después. Añade scheduled_at para programar el envío. Fijamos la versión publicada, el idioma seleccionado y los valores de los parámetros en el momento de la aceptación. Si eliminas la plantilla antes de la hora de envío, el mensaje se rechaza con generation_failure.
  • Un envío usa la versión publicada de la plantilla. Los borradores nunca se envían. Una plantilla desconocida se rechaza con un 404, y una plantilla sin versión publicada con un 422.
  • language selecciona uno de los idiomas de la plantilla. Omítelo para enviar el idioma predeterminado de la plantilla. Si pides uno que la plantilla no tiene, su propia configuración on_missing_language decide si se envía la coincidencia más cercana o se rechaza el envío. Una plantilla que establece language_source_required rechaza un envío que no nombre ningún idioma.
  • La categoría de la plantilla es un valor predeterminado, y la tuya lo sobrescribe. Omite category y el envío hereda la de la plantilla, así que una plantilla transaccional no necesita repetirla en cada llamada.
Plantillas de correo electrónico cubre la creación, publicación y los constructos que una plantilla puede contener.

Tags vs metadata

Ambos adjuntan tus propios datos a un envío y difieren en cómo los consultas después:
  • tags son pares {name, value} estructurados: hasta 20 por envío, nombre de hasta 32 caracteres, valor de hasta 64, solo letras ASCII, dígitos, guion bajo y guion, y nombres únicos dentro del envío. Los tags son dimensiones de filtro, así que puedes filtrar la lista de mensajes por tag y segmentar analíticas y resúmenes de dashboard por tag. Úsalos para etiquetas de baja cardinalidad como campaign, experiment_variant o source.
  • metadata es un objeto JSON arbitrario, de hasta 2 KB serializados. Lo almacenamos, lo devolvemos en lecturas API y lo incluimos en cada evento de webhook, así que es adecuado para contexto que quieres que te devolvamos: IDs internos, claves foráneas, payloads estructurados.
Cada evento de webhook incluye ambos junto con los IDs de correlación (email_id, recipient_id), para que puedas conciliar contra tus propios registros sin una segunda consulta. Los nombres de tag y las claves de metadata de nivel superior que comienzan con __bird se rechazan. No necesitas codificar dispositivo, geografía, proveedor de buzón, tipo de rebote o dominio del destinatario en ninguno de los dos campos, porque ya capturamos cada uno como una dimensión de analíticas.
Ejemplo de código
{
  "tags": [{ "name": "campaign", "value": "onboarding" }],
  "metadata": { "user_id": "usr_12345", "order_id": "ord_98765" }
}

Adjuntos

attachments acepta hasta 20 archivos por mensaje, como bytes codificados en base64 inline. Rechazamos un envío cuyo tamaño estimado de mensaje generado supere 20 MB, medido después de la codificación base64, así que mantén el contenido de adjuntos sin codificar en 15 MB o menos para dejar margen. Adjuntos tiene el contrato de campos, imágenes inline, los tipos de archivo bloqueados y cómo descargar un adjunto.

Qué significa un 202

Un envío exitoso devuelve 202 Accepted con un ID de mensaje con prefijo em_ y status: accepted:
Ejemplo de código
{
  "id": "em_01ky7ma8y2es1s2akzk53tmjn0",
  "status": "accepted",
  "category": "marketing",
  "from": { "email": "hello@yourdomain.com" },
  "to": [{ "email": "delivered@messagebird.dev" }],
  "subject": "Hello from Bird",
  "accepted_count": 1,
  "processed_count": 0,
  "delivered_count": 0,
  "deferred_count": 0,
  "bounced_count": 0,
  "complained_count": 0,
  "rejected_count": 0,
  "open_count": 0,
  "click_count": 0,
  "track_opens": true,
  "track_clicks": true,
  "created_at": "2026-07-23T13:58:20.866Z"
}
El 202 significa que hemos aceptado el envío de forma duradera. Los errores que puedes corregir llegan en la propia solicitud como un 422: un dominio de remitente no verificado o un campo que no pasa la validación. Los resultados por destinatario (entregado, rebotado, diferido, reclamado) llegan después a través de webhooks y los endpoints de lectura del mensaje.
De ahí se desprenden dos cosas:
  • Las lecturas devuelven el estado sin el cuerpo. GET /v1/email/messages/{message_id} devuelve el estado del mensaje y del destinatario, nunca el cuerpo html o text. Cuando el almacenamiento de contenido está habilitado en el espacio de trabajo, los cuerpos almacenados permanecen disponibles hasta 30 días desde GET /v1/email/messages/{message_id}/content.
  • Una lectura puede ir brevemente detrás del envío. Un 404 en los endpoints de lectura justo después de un 202 significa que el mensaje aún no es visible, así que reintenta en un momento.

Reintentar de forma segura

Envía un encabezado Idempotency-Key con un valor único por envío lógico. Si una solicitud tuvo éxito pero nunca viste la respuesta, reprodúcela con la misma clave. El API devuelve el resultado original en lugar de enviar un segundo correo e incluye un encabezado Idempotency-Replay. Idempotencia tiene el formato de clave y la retención.

Envío en lote

Para reducir las solicitudes API, POST /v1/email/batches acepta hasta 100 mensajes independientes y los valida como una sola unidad. También se admite llamar al endpoint de envío individual en un bucle. Cada elemento del lote usa el payload de esta página, incluido scheduled_at, por lo que un lote puede mezclar mensajes inmediatos y programados.

Facturación

Los envíos de correo se miden por destinatario contra la cuota mensual de tu plan, así que un mensaje a tres destinatarios consume tres envíos. Facturación y uso cubre el modelo de medición y la lectura de uso en tiempo real.

Próximos pasos