Novedad: campañas, automatizaciones y correos del producto en la misma cuenta. Conoce Postal
Iniciar sesión Crear cuenta

Migrar de SendGrid a Postal

SendGrid y Postal envían el mismo correo electrónico, pero la llamada tiene otro formato: en SendGrid, destinatarios, variables y argumentos van dentro de personalizations; en Postal, son campos del propio correo. Esta guía compara campo por campo y dice qué no tiene equivalente.

Cambia en el código: envío y webhooks
Reescribir
Se pueden ejecutar las dos a la vez
Sí
Tiempo estimado, según lo que uses
De horas a días
Tipo de envío, más marketing
API y SMTP
1

Verifica el dominio en Postal

Registra el dominio en el panel y crea los registros que muestra. Por sus nombres, los registros de Postal no chocan con los de SendGrid, y los dos quedan verificados al mismo tiempo durante el cambio. Si ya tienes un _dmarc, conserva el tuyo.

  • Postal comprueba los registros por sí sola durante hasta 72 horas y avisa por webhook (domain.updated)
  • Crea una clave de API (empieza con ps_). Para sistemas que solo envían, usa una clave solo de envío, restringida al dominio
Los nombres de los registros son diferentes, así que los dos conviven en el mismo dominio
RegistroSendGrid (seguridad automática)Postal
DKIMCNAME en s1._domainkey y s2._domainkeyTXT en postal._domainkey
Return-Path y SPFCNAME en em y un número (por ejemplo, em1234)MX y TXT en postal
DMARCTXT en _dmarcOpcional. El mismo _dmarc sirve para los dos
2

Reescribe la llamada de envío

Postal no acepta el formato /v3/mail/send: la llamada pasa a ser POST /emails, con los campos en el primer nivel. A continuación, el mismo correo electrónico en los dos formatos. Quien usa el SDK de SendGrid cambia el envío por el SDK de Postal (Node.js, PHP y Laravel) o por HTTP, que es JSON con la clave en el encabezado Authorization.

  • SendGrid responde 202 Accepted; Postal responde 200 con el id del correo electrónico. Guarda el id: es el que aparece en los webhooks y en GET /emails/{id}
  • Varios destinatarios en el mismo to forman un único correo electrónico en el que todos se ven. Para un correo por persona, usa el envío por lotes
  • Repetir la llamada con la misma Idempotency-Key no duplica el envío

Antes: SendGrid

POST /v3/mail/send
POST https://api.sendgrid.com/v3/mail/send
Authorization: Bearer <clave de SendGrid>

{
  "personalizations": [{
    "to": [{ "email": "[email protected]", "name": "Ana" }],
    "custom_args": { "order_id": "10482" }
  }],
  "from": { "email": "[email protected]", "name": "Loja Exemplo" },
  "subject": "Tu pedido #10482 salió para la entrega",
  "content": [
    { "type": "text/plain", "value": "El pedido #10482 está en camino." },
    { "type": "text/html", "value": "<p>El pedido <b>#10482</b> está en camino.</p>" }
  ],
  "categories": ["entrega"]
}

Después: Postal

POST /emails
POST https://api.postal.so/emails
Authorization: Bearer ps_sua_chave
Idempotency-Key: pedido-10482-saiu

{
  "from": "Loja Exemplo <[email protected]>",
  "to": ["Ana <[email protected]>"],
  "subject": "Tu pedido #10482 salió para la entrega",
  "text": "El pedido #10482 está en camino.",
  "html": "<p>El pedido <b>#10482</b> está en camino.</p>",
  "tags": [
    { "name": "category", "value": "entrega" },
    { "name": "order_id", "value": "10482" }
  ]
}
200{ "id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b" }

En el código

Node.js: de @sendgrid/mail al SDK de Postal
- const sgMail = require('@sendgrid/mail');- sgMail.setApiKey(process.env.SENDGRID_API_KEY);- await sgMail.send({-   to: '[email protected]',-   from: '[email protected]',-   subject, html,- });+ import { Postal } from 'postal-so';+ const postal = new Postal(); // lee POSTAL_API_KEY+ await postal.emails.send({+   from: 'Loja Exemplo <[email protected]>',+   to: '[email protected]',+   subject, html,+ });
3

Mapea los campos

La tabla sirve para el código nuevo y como lista de comprobación de lo que usa hoy tu sistema. Las filas que cambian la forma de armar el correo electrónico son las de destinatarios por lotes, categorías y argumentos personalizados.

Mail Send: campo por campo
En SendGridEn PostalObservación
POST /v3/mail/sendPOST /emailsLa clave va en Authorization: Bearer en ambos. En Postal empieza con ps_.
personalizations[].to, lista de {email, name}to: texto o lista de hasta 50, en el formato Nome <email>Una lista es un único correo electrónico, en el que todos se ven.
Varias personalizations (un correo por elemento)POST /emails/batch: lista de hasta 100 correos electrónicos, cada uno con su propio toEn SendGrid el límite es 1.000 personalizations por solicitud. El lote es un recurso del plan y se valida antes: o todos entran en la cola, o ninguno.
cc y bcccc y bccMismo comportamiento: el bcc no aparece para los demás.
from: {email, name}from: texto Nome <email>El dominio debe estar verificado (y ser el de la clave, si esta es restringida).
reply_to y reply_to_listreply_to: texto o lista
subject, global o por personalizaciónsubjectAsunto distinto por persona: un elemento por correo electrónico en el lote, o una plantilla con variable en el asunto.
content: lista de {type, value}html y textCampos separados. Sin text, html es obligatorio; envía los dos cuando puedas.
attachments: content, filename, type, disposition, content_idattachments: content, filename, content_type, content_id y path (URL)type pasa a ser content_type. Imagen incrustada: content_id y cid: en el HTML. No existe disposition. Adjuntos de hasta 40 MB en total, no valen en el lote y dependen del plan.
template_id y dynamic_template_datatemplate: {id, variables}Mira “Lo que no tiene equivalente”: las plantillas de Postal solo sustituyen variables.
headersheadersCada plataforma tiene su lista de encabezados que no se pueden cambiar. En Postal: From, To, Cc, Bcc, Subject, Reply-To, Sender, Date, Message-ID, Return-Path, MIME-Version, Content-Type, Content-Transfer-Encoding y DKIM-Signature.
categories (hasta 10)tags: [{name, value}] (hasta 50)Nombre y valor solo aceptan letras ASCII, números, _ y - (hasta 256 caracteres): “Promoción de Navidad” se convierte en promocao-de-natal.
custom_argstagsMisma regla de caracteres. En los webhooks, las etiquetas llegan en data.tags ({"order_id": "10482"}), y no como claves sueltas en el evento. Para datos libres, guárdalos en tu base de datos junto con el id del correo electrónico.
send_at, Unix, hasta 72 horasscheduled_at, ISO 8601, hasta 30 díasEjemplo: 2026-10-25T09:00:00-03:00. No vale en el lote y depende del plan. Se puede reprogramar (PATCH /emails/{id}) y cancelar (POST /emails/{id}/cancel).
mail_settings.sandbox_modeDirecciones de prueba: [email protected] y otrasEn lugar de un modo de la solicitud, cambias el destinatario. El correo electrónico se simula, no usa cuota y genera webhooks reales.
tracking_settings.open_tracking y click_trackingopen_tracking y click_tracking en el dominioSe activan por dominio (panel o PATCH /domains/{id}), no por correo electrónico. Dependen del plan.
4

Si usas SMTP, cambia la conexión

Quien envía por SMTP no reescribe nada: cambia el servidor, el usuario y la contraseña. El usuario fijo es postal, y no apikey. Postal también acepta 2465 (TLS) y 2587 (STARTTLS) para redes que bloquean los puertos habituales.

  • El encabezado X-SMTPAPI (category, unique_args, sub, send_at, filters…) no lo interpreta Postal: sigue como un encabezado común. Lo que uses de él tiene que pasar a la API
  • Los destinatarios son los del sobre (RCPT TO), hasta 50 por mensaje. Quien no aparece en To ni en Cc se trata como Bcc
  • El SMTP de Postal no tiene etiquetas, programación ni plantillas. Cuotas, lista de supresión, eventos y webhooks son los mismos de la API
  • Mensajes de hasta 40 MB; Postal responde 250 2.0.0 OK queued as <id>, y el id es el del correo electrónico en la API
Configuración SMTP
- Servidor: smtp.sendgrid.net- Puerto: 587 (o 25, 2525, 465)- Usuario: apikey- Contraseña: la clave de API de SendGrid+ Servidor: smtp.postal.so+ Puerto: 465 (TLS directo) o 587 (STARTTLS)+ Usuario: postal+ Contraseña: la clave de API de Postal (ps_…)
5

Recrea los webhooks

SendGrid envía el Event Webhook firmado con una clave pública (ECDSA); Postal firma con un secreto por endpoint, en el estándar Standard Webhooks. El cuerpo también cambia: un evento por solicitud, con el correo electrónico dentro de data.

Eventos: qué corresponde a qué
Evento de SendGridEvento de PostalObservación
processedemail.sentAproximado: Postal avisa cuando entregó el correo electrónico al proveedor de envío.
deliveredemail.deliveredEl servidor del destinatario aceptó el mensaje.
deferredemail.delivery_delayedLa entrega se aplazó y los nuevos intentos ocurren solos.
bounceemail.bounceddata.bounce trae type (Permanent, Transient o Undetermined) y message. Un rebote permanente pone la dirección en la lista de supresión.
droppedemail.suppressed o email.failedAproximado: suppressed cuando el destinatario estaba en la lista de supresión; failed, con failed.reason, cuando no fue posible enviar.
openemail.openedSolo con el seguimiento de aperturas activado en el dominio.
clickemail.clickeddata.click trae link, ipAddress, userAgent y timestamp. Solo con el seguimiento de clics activado.
spamreportemail.complainedLa dirección pasa a la lista de supresión.
unsubscribecontact.updatedLa baja de un contacto de marketing genera contact.updated.
group_unsubscribe y group_resubscribesin evento propioLos temas de Postal no tienen un evento con ese nombre.
Webhooks: formato, firma e intentos
En SendGridEn PostalObservación
Un POST con la lista de eventosUn POST por evento: {type, created_at, data}Los ejemplos de SendGrid muestran un array de eventos. Si tu código recorre una lista, pasará a recibir un objeto.
sg_message_id, email, timestampdata.email_id, data.to, created_atEl email_id es el id devuelto por POST /emails. La hora llega en ISO 8601, y no en Unix.
Firma ECDSA: X-Twilio-Email-Event-Webhook-Signature y -TimestampStandard Webhooks, HMAC-SHA256: webhook-id, webhook-timestamp y webhook-signatureCambia la verificación por el verifyWebhook del SDK o por el ejemplo de la documentación. El secreto (whsec_…) es de cada endpoint. La documentación de webhooks de Postal no tiene el OAuth 2.0 opcional de SendGrid.
Reintentos durante hasta 24 horas, respuesta 2xx8 intentos en unas 27 horas, respuesta 2xx en hasta 15 segundosTodos con el mismo webhook-id: úsalo para descartar duplicados. Reenvío manual por el panel o por POST /webhooks/{id}/events/{event_id}/replay.
Event Webhook en Settings, Mail SettingsWebhooks, en el panel, o POST /webhooksLa URL debe ser HTTPS y pública. Eliges los eventos de cada endpoint.
Node.js: verificar una entrega
import express from 'express';
import { verifyWebhook, PostalWebhookVerificationError } from 'postal-so';

const app = express();
// El cuerpo debe llegar crudo (Buffer) para que la firma coincida.
app.post('/webhooks/postal', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = verifyWebhook(req.body, req.headers, process.env.POSTAL_WEBHOOK_SECRET);
    if (event.type === 'email.bounced') {
      // descarta la dirección
    }
    res.sendStatus(200);
  } catch (error) {
    if (error instanceof PostalWebhookVerificationError) {
      return res.sendStatus(400);
    }
    throw error;
  }
});
6

Lleva las supresiones y los contactos

Primero la lista de supresión, para no escribir a quien no debe recibir. Después los contactos, si usas la audiencia de marketing. SendGrid exporta cada tipo de supresión por una ruta propia; Postal tiene una sola lista.

  • Haz la importación de contactos después de la supresión: las direcciones que ya están en la lista de supresión se ignoran y no usan el límite de contactos del plan
  • Las campañas y automatizaciones de marketing hay que recrearlas en Postal; lo que llevas son los contactos y el contenido
POST /suppressions/batch/add
curl -X POST https://api.postal.so/suppressions/batch/add \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["[email protected]", "[email protected]"]}'
Supresiones y contactos
En SendGridEn PostalObservación
Rebotes: GET /v3/suppression/bounces, hasta 500 por página (limit, offset)POST /suppressions/batch/addLee el email de cada elemento y envía 100 por solicitud. Las direcciones que vuelvan a rebotar en Postal entran solas.
Quejas de spam: GET /v3/suppression/spam_reportsPOST /suppressions/batch/addQuien se quejó no debe recibir de nuevo: llévalo también.
Blocks: GET /v3/suppression/blocksNo importesEn SendGrid, el bloqueo viene de un problema en el mensaje o en la IP de envío y, por defecto, no suprime envíos futuros. Postal no tiene una lista equivalente.
Bajas globales: GET /v3/suppression/unsubscribesContactos con unsubscribed: true (columna de baja en el CSV)Quien salió solo del marketing queda en la audiencia sin recibir campañas. Si la persona no debe recibir nada, ni correos electrónicos del sistema, pon la dirección en la lista de supresión: no se convierte en contacto.
Grupos de baja (ASM)Temas, con elección de cada contacto (opt_in u opt_out)La importación en CSV puede suscribir a todos los contactos importados a temas.
Contactos: PUT /v3/marketing/contacts, hasta 30.000 por solicitudPOST /contacts/batch, hasta 10.000Por encima de 1.000 elementos el lote pasa a la cola (202) y lo sigues en GET /contacts/batches/{id}. El lote crea o actualiza.
Importar CSV: PUT /v3/marketing/contacts/importsImportar CSV en el panel, o POST /contacts/importsArchivo de hasta 20 MB. Quien ya existe se actualiza por el correo electrónico.
Exportar: POST /v3/marketing/contacts/exports (CSV o JSON)Exporta en CSV e importaLa exportación de SendGrid es asíncrona: descarga los archivos y usa el CSV en la importación.
ListasSegmentos estáticosEn la importación, elige el segmento. Solo los segmentos estáticos reciben contactos importados.
Campos personalizadosPropiedades del contactoCrea las propiedades antes de importar, porque la importación no crea propiedades nuevas.
7

Ejecuta las dos en paralelo y cambia

Con el dominio verificado en las dos, pasa un flujo cada vez. Invitaciones, restablecimientos de contraseña y códigos deben salir por un solo camino cada vez.

  • Los IDs son de cada plataforma: guarda de qué proveedor salió cada correo electrónico y sigue recibiendo eventos de SendGrid de lo que salió por allí
  • Usa tags (por ejemplo, provider: postal) para separar los correos nuevos en los webhooks
  • Las cuentas nuevas empiezan con un tope de 500 correos por día, que crece con la reputación de la cuenta, y los envíos se pausan con rebotes permanentes por encima del 3% al 5%, según el nivel, o quejas por encima del 0,3%: sube el volumen por etapas
Dominio verificado en las dosUn flujo en PostalCompara entregas y rebotesPasa los demás flujosApaga SendGrid

Lo que no tiene equivalente

Lo que no tiene equivalente, y cómo sortearlo
En SendGridQué hacer en Postal
Plantillas dinámicas con Handlebars ({{#if}}, {{#each}}, dynamic_template_data con listas)Las plantillas de Postal solo sustituyen variables {{{NOME}}} (texto o número; en el HTML, los valores se escapan). Resuelve condicionales y listas en tu sistema y envía el HTML listo en html.
ip_pool_nameNo hay un campo equivalente en la API de envío.
batch_id (cancelar o pausar un grupo de envíos)Cancela cada correo electrónico programado con POST /emails/{id}/cancel.
asm.group_id en el envíoEl envío por POST /emails no acepta tema. Para pedir la baja en correos electrónicos del sistema, usa el encabezado List-Unsubscribe en headers.
footer, subscription_tracking y ganalytics por correo electrónicoSin equivalente en la API de envío: arma el pie y los enlaces con UTM en el HTML. Los correos de campaña de Postal ya llevan el enlace de baja.
bypass_list_management y demás bypass_*No hay forma de ignorar la lista de supresión: los destinatarios suprimidos se quitan del envío y generan email.suppressed.
Seguimiento de aperturas y clics por correo electrónicoVale por dominio, no por mensaje.
disposition de adjuntosNo existe. La imagen incrustada usa content_id.
X-SMTPAPINo se interpreta. Usa la API para etiquetas, programación y variables.
Webhooks con OAuth 2.0No consta en la documentación de webhooks de Postal; la protección es la firma.

Antes de cambiar la clave

  • Dominio verificado en Postal, clave de API creada y guardada en el entorno
  • Llamada de envío reescrita y probada con una dirección de prueba
  • Categorías y argumentos personalizados convertidos en etiquetas (solo letras ASCII, números, _ y -)
  • Plantillas con Handlebars resueltas en tu sistema o recreadas con variables simples
  • Webhooks recreados, firma comprobada y código que leía una lista de eventos ajustado
  • Supresiones y contactos importados, en ese orden
  • Tu código guarda de qué proveedor salió cada correo electrónico
  • Rebotes revisados en los primeros días después del cambio

Preguntas frecuentes

¿Puedo seguir usando @sendgrid/mail?

No para enviar por Postal: Postal no acepta el formato /v3/mail/send. Cambia el envío por el SDK de Postal o por una llamada HTTP a POST /emails.

¿Necesito reescribir todas mis plantillas?

Solo las que usan condicionales o repeticiones de Handlebars. Las plantillas que solo cambian nombres y números pasan a ser plantillas con {{{VARIAVEL}}}. En los envíos por API, Postal solo sustituye variables.

Mi sistema envía por SMTP. ¿Qué cambia?

El servidor (smtp.postal.so), el usuario (postal) y la contraseña (una clave de API de Postal). Si usa el encabezado X-SMTPAPI, esas funciones tienen que pasar a la API.

¿Puedo mantener el dominio autenticado en SendGrid?

Sí. Los registros de DNS tienen nombres diferentes, así que los dos quedan verificados al mismo tiempo durante el cambio.

¿Listo para reescribir la llamada?

Crea tu cuenta y empieza con el plan Gratis. Para planificar la cuota y el orden de la migración, habla con nosotros por el formulario de contacto.