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 paso a paso

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 que están en la lista de supresión del espacio de trabajo no hacen fallar la solicitud. Esta 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 en los envíos inline, con un máximo de 998 caracteres. Proporciona html, text o ambos, cada uno de hasta 524.288 caracteres. Envía ambos siempre que puedas: un cliente que no puede renderizar HTML muestra 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 sirve para todos los destinatarios del envío, y un token sin clave coincidente se renderiza vacío. Para contenido que reutilizas, envía una plantilla.

Incluye parameters, aunque sea como objeto vacío ({}), para procesar el asunto y el cuerpo como Liquid. Omítelo para enviar tokens como {{ animal }} tal cual están escritos. 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 no válida y los tags o filtros no admitidos devuelven 422.

Los valores insertados en HTML se escapan para que no puedan alterar el marcado que los rodea. Para un enlace completo o una URL de imagen, usa {{ link }} sin url_encode. Para un valor dentro de la query de una 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. Cada respuesta de un destinatario llega a todas ellas, así que lo habitual es usar una o dos.

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. Dos 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í.
  • Cualquier valor con un retorno de carro o salto de línea.

Los envíos transaccionales aceptan List-Unsubscribe y List-Unsubscribe-Post. Los envíos de marketing usan de forma predeterminada la cancelación de suscripción gestionada por Bird.

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. Estas opciones no modifican los píxeles de seguimiento ni los enlaces que ya contiene el contenido que envías. Seguimiento y métricas explica lo que cada una 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 los demás casos, así que establece transactional explícitamente para recibos, restablecimientos de contraseña y otro correo operativo. Categorías explica cómo elegir. El correo enviado por SMTP toma en cambio 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 explícitamente por el pool compartido. 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; se rechazan los nombres reservados (consulta encabezados personalizados)
parametersobjectnoValores para {{ tokens }} en contenido inline; hasta 16 KB serializados; compartidos entre destinatarios
tags{name, value}[]noHasta 20; nombre ≤ 32 caracteres, valor ≤ 64 caracteres; solo [A-Za-z0-9_-]; nombres únicos por envío
metadataobjectnoJSON arbitrario, hasta 2 KB serializados
referencestringno1–255 caracteres; coincidencia exacta para ?reference= en la lista de mensajes
track_opensbooleannoPredeterminado true
track_clicksbooleannoPredeterminado true
categorystringnomarketing o transactional; en un envío con plantilla, la de la plantilla por defecto; en otro caso, marketing
ip_pool_idstringnoipp_... o ipp_shared; omítelo para usar 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; consulta adjuntos
scheduled_atmarca de tiempo RFC 3339noPrograma contenido inline o un template; consulta envío programado

Enviar con una plantilla

Las plantillas pueden usar valores bird.contact.* del contacto que coincide con cada dirección To. Los destinatarios Cc y Bcc reciben una copia del correo personalizado de cada destinatario To. Esto también funciona en el API por lotes y en los envíos programados; consulta personalizar con variables para ver un ejemplo con varios destinatarios.

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. En template.parameters colocas los valores de los parámetros propios de la plantilla, identificados por nombre. Si omites uno, el envío se rechaza con un 422 que lo nombra. El envío admite los mismos tags, metadata, seguimiento y adjuntos que el correo inline. Lo 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. Guardamos la versión publicada, el idioma seleccionado, los parámetros de quien llama y los valores de contacto en el momento de la aceptación, de modo que las ediciones posteriores de contactos o valores de respaldo no cambian los mensajes ya aceptados. Si eliminas la plantilla antes de la hora de envío, el mensaje se rechaza con generation_failure.
  • Personaliza con atributos de contacto. Consulta requisitos de contacto y saludos predeterminados.
  • La coincidencia de contactos ignora los nombres para mostrar. Las direcciones de correo se convierten a minúsculas, conservando puntos y etiquetas con signo más. Si varios contactos comparten esa dirección, el envío devuelve EmailTemplateContactAmbiguous; actualiza los registros duplicados antes de reintentar.
  • El envío usa permisos de correo electrónico. Una clave con emails:write puede enviar una plantilla publicada sin permiso de descubrimiento de contactos. emails:read da acceso al contenido no sensible del mensaje resultante. El descubrimiento de contactos y de propiedades mantiene sus permisos actuales.
  • 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.

Leer el contenido de un destinatario

El historial de mensajes con plantilla usa los valores de contacto guardados cuando se aceptó el envío, aunque después edites o elimines el contacto. Cada entrega copiada tiene su propio ID de destinatario.

Lista los destinatarios del mensaje para obtener sus IDs y luego pasa recipient_id a Get stored message content. Un mensaje nuevo con plantilla que tiene instantáneas de varios destinatarios devuelve 422 si falta ese selector. Si hay un solo destinatario, se selecciona automáticamente; un destinatario que pertenece a otro mensaje devuelve 404.

Las respuestas GET y de listado de mensajes usan el asunto aceptado para el primer destinatario To, identificado por subject_recipient_id. El listado de destinatarios proporciona el asunto propio de cada destinatario. Estos asuntos siguen disponibles cuando el almacenamiento del cuerpo está desactivado. Las lecturas de contenido siguen respetando la configuración de almacenamiento y retención del mensaje: 404 si no se almacenó, 425 mientras está pendiente y 410 tras la caducidad.

Si una instantánea de destinatario retenida deja de estar disponible, el estado del mensaje y el asunto representativo siguen siendo legibles, pero se omiten los asuntos por destinatario y el contenido no está disponible. Las lecturas no consultan los valores actuales de los contactos para reconstruir un envío anterior.

Tags vs metadata

Estos campos 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.
  • reference es una cadena de 1 a 255 caracteres por la que quieres encontrar el mensaje, como un número de pedido o de ticket. GET /v1/email/messages?reference= la busca con coincidencia exacta, distinguiendo mayúsculas y minúsculas, y varios mensajes pueden compartir el mismo valor. Se devuelve en las lecturas y se incluye en los eventos de webhook del mensaje. Establécelo solo si necesitas buscar por él, y pon cualquier otro dato en metadata.

Cada evento de webhook incluye los tags y la metadata junto con los IDs de correlación (email_id, recipient_id), para que puedas conciliar con tus propios registros sin una segunda consulta. Se rechazan los nombres de tag y las claves de metadata de nivel superior que comienzan con __bird. No necesitas codificar el dispositivo, la geografía, el proveedor de buzón, el tipo de rebote ni el dominio del destinatario en ninguno de los dos campos, porque ya capturamos cada uno de ellos 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" },
  "reference": "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.

Coste y facturación

Los envíos de correo se miden por destinatario, así que un mensaje a tres destinatarios cuenta como tres envíos. En el plan Email Free, los envíos se descuentan de su cuota mensual y diaria. En un plan Email de pago, cada envío se cobra de tu monedero según la tarifa del plan por cada 1.000 correos. Facturación y uso cubre el modelo de medición y la lectura de uso en tiempo real, y la página de precios de correo electrónico indica la tarifa de cada plan.

Próximos pasos