Novo: campanhas, automações e e-mails do produto na mesma conta. Conheça a Postal
Entrar Criar conta

Migrar do SendGrid para a Postal

O SendGrid e a Postal enviam o mesmo e-mail, mas a chamada tem outro formato: no SendGrid, destinatários, variáveis e argumentos ficam dentro de personalizations; na Postal, são campos do próprio e-mail. Este guia compara campo a campo e diz o que não tem equivalente.

Muda no código: envio e webhooks
Reescrever
Dá para rodar as duas juntas
Sim
Tempo estimado, conforme o que você usa
Horas a dias
Tipo de envio, mais marketing
API e SMTP
1

Verifique o domínio na Postal

Cadastre o domínio no painel e crie os registros que ele mostra. Pelos nomes, os registros da Postal não colidem com os do SendGrid, e os dois ficam verificados ao mesmo tempo durante a virada. Se você já tem um _dmarc, mantenha o seu.

  • A Postal confere os registros sozinha por até 72 horas e avisa por webhook (domain.updated)
  • Crie uma chave de API (ela começa com ps_). Para sistemas que só enviam, use uma chave só de envio, restrita ao domínio
Os nomes dos registros são diferentes, então os dois convivem no mesmo domínio
RegistroSendGrid (segurança automática)Postal
DKIMCNAME em s1._domainkey e s2._domainkeyTXT em postal._domainkey
Return-Path e SPFCNAME em em e um número (por exemplo, em1234)MX e TXT em postal
DMARCTXT em _dmarcOpcional. O mesmo _dmarc vale para os dois
2

Reescreva a chamada de envio

A Postal não aceita o formato /v3/mail/send: a chamada passa a ser POST /emails, com os campos no primeiro nível. Abaixo, o mesmo e-mail nos dois formatos. Quem usa o SDK do SendGrid troca o envio pelo SDK da Postal (Node.js, PHP e Laravel) ou por HTTP, que é JSON com a chave no cabeçalho Authorization.

  • O SendGrid responde 202 Accepted; a Postal responde 200 com o id do e-mail. Guarde o id: é ele que aparece nos webhooks e em GET /emails/{id}
  • Vários destinatários no mesmo to formam um único e-mail em que todos se veem. Para um e-mail por pessoa, use o envio em lote
  • Repetir a chamada com a mesma Idempotency-Key não duplica o envio

Antes: SendGrid

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

{
  "personalizations": [{
    "to": [{ "email": "[email protected]", "name": "Ana" }],
    "custom_args": { "order_id": "10482" }
  }],
  "from": { "email": "[email protected]", "name": "Loja Exemplo" },
  "subject": "Seu pedido #10482 saiu para entrega",
  "content": [
    { "type": "text/plain", "value": "O pedido #10482 está a caminho." },
    { "type": "text/html", "value": "<p>O pedido <b>#10482</b> está a caminho.</p>" }
  ],
  "categories": ["entrega"]
}

Depois: 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": "Seu pedido #10482 saiu para entrega",
  "text": "O pedido #10482 está a caminho.",
  "html": "<p>O pedido <b>#10482</b> está a caminho.</p>",
  "tags": [
    { "name": "category", "value": "entrega" },
    { "name": "order_id", "value": "10482" }
  ]
}
200{ "id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b" }

No código

Node.js: do @sendgrid/mail para o SDK da 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(); // lê POSTAL_API_KEY+ await postal.emails.send({+   from: 'Loja Exemplo <[email protected]>',+   to: '[email protected]',+   subject, html,+ });
3

Mapeie os campos

A tabela vale para o código novo e como lista de conferência do que o seu sistema usa hoje. As linhas que mudam o jeito de montar o e-mail são as de destinatários em lote, categorias e argumentos personalizados.

Mail Send: campo a campo
No SendGridNa PostalObservação
POST /v3/mail/sendPOST /emailsA chave vai em Authorization: Bearer nos dois. Na Postal ela começa com ps_.
personalizations[].to, lista de {email, name}to: texto ou lista de até 50, no formato Nome <email>Uma lista é um único e-mail, em que todos se veem.
Várias personalizations (um e-mail por item)POST /emails/batch: lista de até 100 e-mails, cada um com o seu toNo SendGrid o limite é 1.000 personalizations por requisição. O lote é um recurso do plano e é validado antes: ou todos entram na fila, ou nenhum.
cc e bcccc e bccMesmo comportamento: o bcc não aparece para os outros.
from: {email, name}from: texto Nome <email>O domínio precisa estar verificado (e ser o da chave, se ela for restrita).
reply_to e reply_to_listreply_to: texto ou lista
subject, global ou por personalizaçãosubjectAssunto diferente por pessoa: um item por e-mail no lote, ou um template com variável no assunto.
content: lista de {type, value}html e textCampos separados. Sem text, o html é obrigatório; mande os dois quando puder.
attachments: content, filename, type, disposition, content_idattachments: content, filename, content_type, content_id, e path (URL)type vira content_type. Imagem embutida: content_id e cid: no HTML. Não existe disposition. Anexos até 40 MB no total, não valem no lote e dependem do plano.
template_id e dynamic_template_datatemplate: {id, variables}Veja “O que não tem equivalente”: os templates da Postal só substituem variáveis.
headersheadersCada plataforma tem a sua lista de cabeçalhos que não podem ser trocados. Na Postal: From, To, Cc, Bcc, Subject, Reply-To, Sender, Date, Message-ID, Return-Path, MIME-Version, Content-Type, Content-Transfer-Encoding e DKIM-Signature.
categories (até 10)tags: [{name, value}] (até 50)Nome e valor só aceitam letras ASCII, números, _ e - (até 256 caracteres): “Promoção de Natal” vira promocao-de-natal.
custom_argstagsMesma regra de caracteres. Nos webhooks, as tags vêm em data.tags ({"order_id": "10482"}), e não como chaves soltas no evento. Para dados livres, guarde no seu banco junto com o id do e-mail.
send_at, Unix, até 72 horasscheduled_at, ISO 8601, até 30 diasExemplo: 2026-10-25T09:00:00-03:00. Não vale no lote e depende do plano. Dá para reagendar (PATCH /emails/{id}) e cancelar (POST /emails/{id}/cancel).
mail_settings.sandbox_modeEndereços de teste: [email protected] e outrosEm vez de um modo da requisição, você troca o destinatário. O e-mail é simulado, não usa cota e gera webhooks reais.
tracking_settings.open_tracking e click_trackingopen_tracking e click_tracking no domínioLigados por domínio (painel ou PATCH /domains/{id}), não por e-mail. Dependem do plano.
4

Se você usa SMTP, troque a conexão

Quem manda por SMTP não reescreve nada: troca o servidor, o usuário e a senha. O usuário fixo é postal, e não apikey. A Postal também aceita 2465 (TLS) e 2587 (STARTTLS) para redes que bloqueiam as portas comuns.

  • O cabeçalho X-SMTPAPI (category, unique_args, sub, send_at, filters…) não é interpretado pela Postal: ele segue como um cabeçalho comum. O que você usa dele precisa ir para a API
  • Os destinatários são os do envelope (RCPT TO), até 50 por mensagem. Quem não aparece em To nem em Cc é tratado como Bcc
  • O SMTP da Postal não tem tags, agendamento nem templates. Cotas, lista de supressão, eventos e webhooks são os mesmos da API
  • Mensagens até 40 MB; a Postal responde 250 2.0.0 OK queued as <id>, e o id é o do e-mail na API
Configuração SMTP
- Servidor: smtp.sendgrid.net- Porta: 587 (ou 25, 2525, 465)- Usuário: apikey- Senha: a chave de API do SendGrid+ Servidor: smtp.postal.so+ Porta: 465 (TLS direto) ou 587 (STARTTLS)+ Usuário: postal+ Senha: a chave de API da Postal (ps_…)
5

Recrie os webhooks

O SendGrid manda o Event Webhook assinado com uma chave pública (ECDSA); a Postal assina com um segredo por endpoint, no padrão Standard Webhooks. O corpo também muda: um evento por requisição, com o e-mail dentro de data.

Eventos: o que corresponde a quê
Evento do SendGridEvento da PostalObservação
processedemail.sentAproximado: a Postal avisa quando entregou o e-mail ao provedor de envio.
deliveredemail.deliveredO servidor do destinatário aceitou a mensagem.
deferredemail.delivery_delayedA entrega foi adiada e novas tentativas acontecem sozinhas.
bounceemail.bounceddata.bounce traz type (Permanent, Transient ou Undetermined) e message. Bounce permanente põe o endereço na lista de supressão.
droppedemail.suppressed ou email.failedAproximado: suppressed quando o destinatário estava na lista de supressão; failed, com failed.reason, quando não foi possível enviar.
openemail.openedSó com o rastreamento de aberturas ligado no domínio.
clickemail.clickeddata.click traz link, ipAddress, userAgent e timestamp. Só com o rastreamento de cliques ligado.
spamreportemail.complainedO endereço vai para a lista de supressão.
unsubscribecontact.updatedO descadastro de um contato de marketing gera contact.updated.
group_unsubscribe e group_resubscribesem evento próprioOs tópicos da Postal não têm um evento com esse nome.
Webhooks: formato, assinatura e tentativas
No SendGridNa PostalObservação
Um POST com a lista de eventosUm POST por evento: {type, created_at, data}Os exemplos do SendGrid mostram um array de eventos. Se o seu código percorre uma lista, ele passa a receber um objeto.
sg_message_id, email, timestampdata.email_id, data.to, created_atO email_id é o id devolvido por POST /emails. O horário vem em ISO 8601, e não em Unix.
Assinatura ECDSA: X-Twilio-Email-Event-Webhook-Signature e -TimestampStandard Webhooks, HMAC-SHA256: webhook-id, webhook-timestamp e webhook-signatureTroque a verificação pelo verifyWebhook do SDK ou pelo exemplo da documentação. O segredo (whsec_…) é de cada endpoint. A documentação de webhooks da Postal não tem o OAuth 2.0 opcional do SendGrid.
Novas tentativas por até 24 horas, resposta 2xx8 tentativas em cerca de 27 horas, resposta 2xx em até 15 segundosTodas com o mesmo webhook-id: use para descartar duplicatas. Reenvio manual pelo painel ou por POST /webhooks/{id}/events/{event_id}/replay.
Event Webhook em Settings, Mail SettingsWebhooks, no painel, ou POST /webhooksA URL precisa ser HTTPS e pública. Você escolhe os eventos de cada endpoint.
Node.js: verificar uma entrega
import express from 'express';
import { verifyWebhook, PostalWebhookVerificationError } from 'postal-so';

const app = express();
// O corpo precisa chegar cru (Buffer) para a assinatura bater.
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') {
      // descarte o endereço
    }
    res.sendStatus(200);
  } catch (error) {
    if (error instanceof PostalWebhookVerificationError) {
      return res.sendStatus(400);
    }
    throw error;
  }
});
6

Leve as supressões e os contatos

Primeiro a lista de supressão, para não escrever a quem não deve receber. Depois os contatos, se você usa a audiência de marketing. O SendGrid exporta cada tipo de supressão por uma rota própria; a Postal tem uma lista só.

  • Faça a importação de contatos depois da supressão: endereços que já estão na lista de supressão são ignorados e não usam o limite de contatos do plano
  • Campanhas e automações do marketing precisam ser recriadas na Postal; o que você leva são os contatos e o conteúdo
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]"]}'
Supressões e contatos
No SendGridNa PostalObservação
Bounces: GET /v3/suppression/bounces, até 500 por página (limit, offset)POST /suppressions/batch/addLeia o email de cada item e envie 100 por requisição. Endereços que voltarem a dar bounce na Postal entram sozinhos.
Reclamações de spam: GET /v3/suppression/spam_reportsPOST /suppressions/batch/addQuem reclamou não deve receber de novo: leve também.
Blocks: GET /v3/suppression/blocksNão importeNo SendGrid, o bloqueio vem de um problema na mensagem ou no IP de envio e, por padrão, não suprime envios futuros. A Postal não tem uma lista equivalente.
Descadastros globais: GET /v3/suppression/unsubscribesContatos com unsubscribed: true (coluna de descadastro no CSV)Quem saiu só do marketing fica na audiência sem receber campanhas. Se a pessoa não deve receber nada, nem e-mails do sistema, ponha o endereço na lista de supressão: ele não vira contato.
Grupos de descadastro (ASM)Tópicos, com escolha de cada contato (opt_in ou opt_out)A importação em CSV pode inscrever todos os contatos importados em tópicos.
Contatos: PUT /v3/marketing/contacts, até 30.000 por requisiçãoPOST /contacts/batch, até 10.000Acima de 1.000 itens o lote vai para a fila (202) e você acompanha em GET /contacts/batches/{id}. O lote cria ou atualiza.
Importar CSV: PUT /v3/marketing/contacts/importsImportar CSV no painel, ou POST /contacts/importsArquivo de até 20 MB. Quem já existe é atualizado pelo e-mail.
Exportar: POST /v3/marketing/contacts/exports (CSV ou JSON)Exporte em CSV e importeA exportação do SendGrid é assíncrona: baixe os arquivos e use o CSV na importação.
ListasSegmentos estáticosNa importação, escolha o segmento. Só segmentos estáticos recebem contatos importados.
Campos personalizadosPropriedades de contatoCrie as propriedades antes de importar, porque a importação não cria propriedades novas.
7

Rode as duas lado a lado e vire

Com o domínio verificado nos dois, passe um fluxo por vez. Convites, redefinições de senha e códigos devem sair por um só caminho de cada vez.

  • Os IDs são de cada plataforma: guarde de qual provedor cada e-mail saiu e continue recebendo eventos do SendGrid para o que saiu por lá
  • Use tags (por exemplo, provider: postal) para separar os e-mails novos nos webhooks
  • Contas novas começam com um teto de 500 e-mails por dia, que cresce com a reputação da conta, e os envios pausam com bounces permanentes acima de 3% a 5%, conforme o nível, ou reclamações acima de 0,3%: suba o volume por etapas
Domínio verificado nos doisUm fluxo na PostalCompara entregas e bouncesPassa os demais fluxosDesliga o SendGrid

O que não tem equivalente

O que não tem equivalente, e como contornar
No SendGridO que fazer na Postal
Templates dinâmicos com Handlebars ({{#if}}, {{#each}}, dynamic_template_data com listas)Os templates da Postal só substituem variáveis {{{NOME}}} (texto ou número; no HTML, os valores são escapados). Resolva condicionais e listas no seu sistema e envie o HTML pronto em html.
ip_pool_nameNão há campo equivalente na API de envio.
batch_id (cancelar ou pausar um grupo de envios)Cancele cada e-mail agendado com POST /emails/{id}/cancel.
asm.group_id no envioO envio por POST /emails não aceita tópico. Para pedir o descadastro em e-mails do sistema, use o cabeçalho List-Unsubscribe em headers.
footer, subscription_tracking e ganalytics por e-mailSem equivalente na API de envio: monte o rodapé e os links com UTM no HTML. Os e-mails de campanha da Postal já levam o link de descadastro.
bypass_list_management e demais bypass_*Não há como ignorar a lista de supressão: destinatários suprimidos são removidos do envio e geram email.suppressed.
Rastreamento de aberturas e cliques por e-mailVale por domínio, não por mensagem.
disposition de anexosNão existe. Imagem embutida usa content_id.
X-SMTPAPINão é interpretado. Use a API para tags, agendamento e variáveis.
Webhooks com OAuth 2.0Não consta da documentação de webhooks da Postal; a proteção é a assinatura.

Antes de virar a chave

  • Domínio verificado na Postal, chave de API criada e guardada no ambiente
  • Chamada de envio reescrita e testada com um endereço de teste
  • Categorias e argumentos personalizados convertidos em tags (só letras ASCII, números, _ e -)
  • Modelos com Handlebars resolvidos no seu sistema ou recriados com variáveis simples
  • Webhooks recriados, assinatura conferida e código que lia uma lista de eventos ajustado
  • Supressões e contatos importados, nessa ordem
  • O seu código guarda de qual provedor cada e-mail saiu
  • Bounces conferidos nos primeiros dias depois da virada

Perguntas frequentes

Posso continuar usando o @sendgrid/mail?

Não para enviar pela Postal: a Postal não aceita o formato /v3/mail/send. Troque o envio pelo SDK da Postal ou por uma chamada HTTP a POST /emails.

Preciso reescrever todos os meus modelos?

Só os que usam condicionais ou repetições do Handlebars. Modelos que só trocam nomes e números viram templates com {{{VARIAVEL}}}. Nos envios por API, a Postal só substitui variáveis.

O meu sistema manda por SMTP. O que muda?

O servidor (smtp.postal.so), o usuário (postal) e a senha (uma chave de API da Postal). Se ele usa o cabeçalho X-SMTPAPI, essas funções precisam ir para a API.

Posso manter o domínio autenticado no SendGrid?

Sim. Os registros de DNS têm nomes diferentes, então os dois ficam verificados ao mesmo tempo durante a virada.

Pronto para reescrever a chamada?

Crie a sua conta e comece no plano Grátis. Para planejar a cota e a ordem da migração, fale com a gente pelo formulário de contato.