Migrar desde Postmark
Esta página mapea el payload de envío, las exportaciones de supresiones y los webhooks de Postmark a Bird. Sigue la guía principal de migración en orden y usa estas correspondencias para los pasos 1, 3 y 4.
La documentación de Postmark indica que "does not currently support HMAC webhook signature verification" (consultado en septiembre de 2026), así que el paso 4 añade verificación que tu handler no tiene hoy. Lee Traducir eventos de webhook antes de planificar el cambio.
Pasa esto a tu agente
Pega esto en Claude Code, Cursor o Codex. El agente recorre esta página contra tu propio repositorio, usando la superficie Bird que ya tenga: el servidor MCP si hay uno conectado, o CLI si está instalado y con sesión iniciada.
Ejemplo de código
I am moving an email integration from Postmark to Bird. Route through it with me.
1. Check what you already have before setting anything up. If Bird's MCP server is connected, use its tools. If the Bird CLI is installed and signed in, use that. Either one is enough, and every step below is an action you take with whichever you have. Only if neither is present, follow https://bird.com/docs/ai/set-up-your-agent.md to set one up and sign me in. Every Bird docs page serves Markdown at its own URL with `.md` appended, so fetch that rather than the HTML.
2. Read https://bird.com/docs/guides/email/migrate/postmark.md for the payload, suppression and webhook mapping, and https://bird.com/docs/guides/email/migrate.md for the order the steps go in.
3. Find and list my Postmark usage in this repository before you change anything: calls to /email and /email/withTemplate and any SDK wrappers around them, every MessageStream name I send on, the webhook handler and the URL it is registered at, and every domain I send from. Tell me the list before you edit anything.
4. Register each of those sending domains with Bird and give me the DNS records to publish, following https://bird.com/docs/guides/email/sending-domains.md. Leave every DNS record Postmark uses exactly as it is: Bird's records are published alongside them and both providers authenticate side by side until I switch traffic. Publishing DNS affects mail for the whole domain, so show me the records and let me publish them.
5. Export my suppressions and import them into Bird before any production traffic goes through Bird, so my first sends do not reach addresses that already bounced or complained. Postmark keeps suppressions per message stream, so dump GET /message-streams/{stream_id}/suppressions/dump once for every stream I send on rather than only the default outbound stream. The Bird import takes one address per request and is idempotent, so a partial re-run is safe. https://bird.com/docs/guides/email/suppressions.md has the reason taxonomy.
6. Port the send call and the webhook handler using the mapping tables on the provider page. Postmark's docs say it does not currently support HMAC webhook signature verification, so my handler probably has no signature check and adding one is new code rather than a swap. If my registered Postmark webhook URL carries HTTP Basic credentials, take them out and tell me to rotate that pair rather than reusing it on the Bird endpoint: a URL-embedded credential should be treated as exposed. https://bird.com/docs/guides/webhooks.md and https://bird.com/docs/guides/email/events.md.
7. Run my whole integration against Bird's mail sandbox before any production traffic, following https://bird.com/docs/guides/email/testing-sandbox.md. Sandbox sends run the real pipeline without reaching an inbox or touching my sending reputation.
8. Stop and ask me wherever a step needs a decision. Do not point production traffic at Bird until I have seen the sandbox results and replied with the words cut over to Bird. Retiring the Postmark path is a separate step that comes later: ask me again and wait for me to reply with the words retire the Postmark path. A reply that agrees without naming what it is authorising is not authorisation. Finish by telling me what is left that only a person can do.Mapear la llamada de envío
Postmark divide el envío en dos endpoints: POST /email para un mensaje compuesto y POST /email/withTemplate para una plantilla almacenada. Nuestro POST /v1/email/messages es un solo endpoint para ambos, con la plantilla referenciada en un campo.
| Función | Postmark | Bird |
|---|---|---|
| Autenticación | Header X-Postmark-Server-Token | Authorization: Bearer |
| Remitente | From | from |
| Destinatarios | To / Cc / Bcc (separados por coma, máx. 50) | to / cc / bcc (arrays) |
| Asunto | Subject | subject |
| Cuerpo | HtmlBody / TextBody | html / text (al menos uno) |
| Reply-to | ReplyTo (separados por coma) | reply_to (array) |
| Headers personalizados | Headers (objetos Name/Value) | headers (objeto string → string) |
| Etiqueta filtrable | Tag (una por mensaje) | tags: pares {name, value} |
| Contexto de ida y vuelta | Metadata | metadata: JSON arbitrario |
| Plantilla almacenada | TemplateId / TemplateAlias + TemplateModel | template + template.parameters |
| Seguimiento de aperturas | TrackOpens | track_opens (por defecto true) |
| Seguimiento de clics | TrackLinks (None/HtmlAndText/HtmlOnly/TextOnly) | track_clicks (booleano, ver más abajo) |
| Adjuntos | Attachments (Name, Content, ContentType) | attachments |
| Separación de tráfico | MessageStream | (sin equivalente, ver más abajo) |
| Categoría | (ninguno) | category: marketing (por defecto) o transactional |
| Programación | (ninguno) | scheduled_at |
Los límites y valores por defecto de nuestros campos (cantidad de destinatarios, límites de tags y metadatos) están en Enviar correo.
Notas de migración:
- Los destinatarios son strings en Postmark y arrays aquí. "a@x.com, b@x.com" pasa a ser ["a@x.com", "b@x.com"]. Si tu código construye ese string uniendo una lista, elimina el join en lugar de la lista.
- Tag es un string por mensaje. Nuestros tags son pares, y puede haber varios. Un tag como "welcome" pasa a ser {"name": "category", "value": "welcome"}. Elige un name estable para que tus dashboards filtren como lo hacían tus estadísticas de tags en Postmark.
- Metadata se porta directamente, y lo devolvemos tal cual. Retornamos tu metadata (y tags) en cada evento de webhook junto con email_id/recipient_id, de modo que tus handlers recuperan tu contexto sin necesidad de una consulta.
- El seguimiento de clics es un enum allí y un booleano aquí. TrackLinks: "None" equivale a track_clicks: false; los tres valores habilitados se convierten todos en track_clicks: true, porque no rastreamos las partes HTML y texto por separado.
- Las plantillas pasan a la misma llamada. No hay un endpoint separado para plantillas: TemplateId o TemplateAlias se convierte en template (por ID o slug) y TemplateModel se convierte en template.parameters, en POST /v1/email/messages. Consulta enviar con una plantilla.
- Un flujo de mensajes no tiene equivalente, y category no lo es. Un flujo es un contenedor con su propia lista de supresiones, sus propias estadísticas y sus propios webhooks. Nuestra categoría es un flag por mensaje con un solo efecto: decide qué registros de supresión y preferencias de cancelación de suscripción pueden bloquear ese mensaje. Establecer category: transactional porque un mensaje venía de un flujo transaccional suele ser correcto, pero es una declaración sobre por qué estás enviando, no una migración del flujo. Nada de esto reproduce estadísticas por flujo ni el alcance de supresiones por flujo; usa tags para la separación en reportes.
Exportar supresiones
Postmark mantiene las supresiones por flujo de mensajes, así que no hay una lista única a nivel de cuenta que puedas extraer. Para cada flujo en el que envíes, expórtalo y pasa el resultado por el bucle de importación:
- GET /message-streams/{stream_id}/suppressions/dump
Enumera tus flujos primero y exporta todos en los que aún envíes. Tratar el flujo outbound por defecto como si fuera la lista completa arrastra las supresiones transaccionales y omite las de broadcast, y lo descubres al enviar correo a personas que se dieron de baja. Los valores de SuppressionReason son HardBounce, SpamComplaint y ManualSuppression, que corresponden a nuestras razones hard_bounce, complaint y manual. Supresiones tiene la taxonomía completa.
Traducir eventos de webhook
Postmark envía un tipo de webhook por evento y lo identifica con el campo RecordType en el payload.
| Resultado | Postmark | Bird |
|---|---|---|
| Aceptado/procesado | (la respuesta de API) | email.accepted → email.processed |
| Entregado | Delivery | email.delivered |
| Fallo temporal | Bounce con Type transitorio | email.deferred |
| Rebote permanente | Bounce con Type: HardBounce | email.bounced / email.out_of_band_bounce |
| Queja de spam | SpamComplaint | email.complained |
| Bloqueado/suprimido | (ninguno) | email.rejected |
| Apertura | Open | email.opened |
| Clic | Click | email.clicked |
| Cancelación de suscripción | SubscriptionChange | email.unsubscribed / email.list_unsubscribed |
Dos diferencias determinan cuánto cambia tu handler.
La verificación es código nuevo, no un reemplazo. La documentación de Postmark indica que "does not currently support HMAC webhook signature verification" (consultado en septiembre de 2026), y recomienda credenciales HTTP Basic embebidas en la URL registrada (https://<username>:<password>@example.com/webhook) junto con sus rangos de IP en tu firewall. Nosotros firmamos cada entrega según el esquema HMAC de Standard Webhooks, así que tu handler gana un paso de verificación que antes no tenía. La receta está en Webhooks y eventos. Haz este paso primero: un handler que acepta solicitudes sin firmar es lo único que la migración no debería arrastrar.
Rota las credenciales en lugar de reutilizarlas. Un usuario y contraseña que han vivido dentro de una URL de webhook deben tratarse como expuestos, porque las URLs llegan a logs de acceso, exportaciones de configuración y consolas de proveedor. Quítalos del endpoint y genera un par nuevo si algo más aún los necesita; no arrastres el par antiguo al endpoint Bird, que autentica por firma.
Postmark reporta rebotes duros y suaves como un solo registro Bounce con un campo Type. Nosotros los reportamos como eventos distintos. Un handler que bifurca según Type dentro de un payload de rebote aquí bifurca según el nombre del evento: los fallos transitorios llegan como email.deferred y los permanentes como email.bounced. Cada tipo de rebote que Postmark distingue está en su referencia Bounce API; lo que importa para la migración es a qué lado de esa división cae cada uno.
Nuestros eventos de entrega tienen alcance por destinatario (recipient_id junto con email_id), así que un envío a tres destinatarios produce tres resultados de entrega en lugar de uno.
Cambio de proveedor
Recorre dominios y DNS y la prueba de humo en sandbox en la guía principal. Ambos son independientes del proveedor.
Próximos pasos
- Dominios de envío: registro, ciclo de vida de verificación y los registros DNS que estás publicando
- Webhooks y eventos: configuración de endpoint y verificación con Standard Webhooks
- Sandbox de pruebas: prueba de humo de la nueva integración antes del cambio
- Supresiones: confirma tu lista importada y cómo la mantenemos a partir de ahora
Recursos relacionados
Continúa con la documentación, guías y ejemplos sobre este tema. Los recursos están en inglés.
Ver la guíaGetting started with emailExplorar la funcionalidadEmailSeguir la ruta de aprendizajeBuild your first integrationGuía de implementaciónSend your first email
Prueba el ejercicio y obtén un resumen de implementación