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

Do Postmark para a Postal, e o que não tem par

O envio por JSON é parecido, mas os nomes dos campos, o cabeçalho da chave, os webhooks e a ideia de Message Streams são diferentes. Este guia mostra o que tem correspondente e o que você precisa organizar de outro jeito.

Message Streams
Sem streams
Autenticação
Token para Bearer
Segurança
Webhooks assinados

Visão geral

No Postmark você envia com POST /email, com o token do servidor em X-Postmark-Server-Token e campos em PascalCase. Na Postal o envio é POST /emails, com Authorization: Bearer ps_… e campos em snake_case. O desenho é parecido, mas há uma diferença de modelo: a Postal não tem Message Streams. Quem separa transacional de marketing por streams precisa organizar isso de outro jeito, e o passo 4 mostra como.

PostmarkPOST api.postmarkapp.com/emailX-Postmark-Server-Token
PostalPOST api.postal.so/emailsAuthorization: Bearer

Muda só o nome

Os campos do envio, os anexos, os cabeçalhos e o SMTP têm correspondente. Os nomes passam de PascalCase para snake_case, e o token vai em Authorization: Bearer.

Muda de lugar

Rastreamento passa a ser do domínio. Metadados viram tags com caracteres restritos. A segurança dos webhooks passa a ser a assinatura de cada entrega.

Não tem equivalente

Message Streams, supressões separadas por stream, templates com blocos e dados aninhados, o token de teste e o lote de 500 mensagens.

A Postal não imita a API do Postmark. A única API com modo de compatibilidade é a da Resend: se você já usa o SDK dela, veja o guia da Resend. Para o Postmark, a troca é no código, com fetch ou com os SDKs oficiais da Postal para Node.js e PHP.

1

Verifique o domínio e crie uma chave

Adicione o domínio no painel (ou com POST /domains), crie na sua DNS os registros que a Postal mostra e peça a verificação. A Postal assina com DKIM no seletor postal e usa um subdomínio postal para o Return-Path, então confira que nenhum registro seu já usa esses nomes e mantenha os do Postmark até a virada terminar.

  • O token do servidor, que só envia, corresponde a uma chave só de envio. Quem também gerencia templates, supressões e webhooks por API precisa de uma chave de acesso total.
  • A chave pode ser restrita a um domínio, o que ajuda a separar sistemas. O token completo aparece uma única vez, na criação.
  • Região: a Postal envia de São Paulo por padrão e deixa escolher us-east-1, us-east-2, eu-west-1 ou ap-northeast-1 em cada domínio.
2

Troque a chamada de envio

Os campos mudam de nome e a chave muda de cabeçalho. Muda também o jeito de saber se deu certo: o Postmark responde com ErrorCode no corpo, e a Postal usa o status HTTP e um erro com statusCode, name e message. A resposta de sucesso é só {"id": "…"}. Com o cabeçalho Idempotency-Key, repetir uma requisição não duplica o envio.

curl · antes e depois
- curl "https://api.postmarkapp.com/email" \-   -X POST \-   -H "Accept: application/json" \-   -H "Content-Type: application/json" \-   -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \-   -d '{-     "From": "[email protected]",-     "To": "[email protected]",-     "Subject": "Seu pedido #10482 saiu para entrega",-     "HtmlBody": "<p>O pedido <b>#10482</b> está a caminho.</p>",-     "Tag": "envio",-     "MessageStream": "outbound"-   }'+ curl -X POST https://api.postal.so/emails \+   -H "Authorization: Bearer $POSTAL_API_KEY" \+   -H "Content-Type: application/json" \+   -d '{+     "from": "Aurora Cosméticos <[email protected]>",+     "to": ["[email protected]"],+     "subject": "Seu pedido #10482 saiu para entrega",+     "html": "<p>O pedido <b>#10482</b> está a caminho.</p>",+     "tags": [{"name": "stream", "value": "transactional"}]+   }'
Node.js · antes e depois
- const response = await fetch('https://api.postmarkapp.com/email', {-   method: 'POST',-   headers: {-     Accept: 'application/json',-     'Content-Type': 'application/json',-     'X-Postmark-Server-Token': process.env.POSTMARK_SERVER_TOKEN,-   },-   body: JSON.stringify({-     From: '[email protected]',-     To: '[email protected]',-     Subject: 'Seu pedido #10482 saiu para entrega',-     HtmlBody: '<p>O pedido <b>#10482</b> está a caminho.</p>',-     MessageStream: 'outbound',-   }),- });- const { MessageID, ErrorCode } = await response.json();+ const response = await fetch('https://api.postal.so/emails', {+   method: 'POST',+   headers: {+     Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,+     'Content-Type': 'application/json',+     'Idempotency-Key': 'order-10482-shipped',+   },+   body: JSON.stringify({+     from: 'Aurora Cosméticos <[email protected]>',+     to: ['[email protected]'],+     subject: 'Seu pedido #10482 saiu para entrega',+     html: '<p>O pedido <b>#10482</b> está a caminho.</p>',+     tags: [{ name: 'stream', value: 'transactional' }],+   }),+ });+ if (!response.ok) { /* status HTTP e corpo { statusCode, name, message } */ }+ const { id } = await response.json();

Campo a campo

Tudo o que o envio do Postmark aceita e o que acontece com cada parte na Postal.

Campos do envio: o que cada um vira.
PostmarkNa PostalO que muda
X-Postmark-Server-TokenAuthorization: Bearer ps_…O Accept: application/json que o Postmark exige não é necessário. A chave pode ser só de envio ou de acesso total, e pode ser restrita a um domínio.
FromfromO Postmark pede uma Sender Signature confirmada. A Postal só aceita remetentes de um domínio verificado e com envio ativo.
To (texto separado por vírgula)to (texto ou lista)Até 50 endereços. No Postmark o limite de 50 soma To, Cc e Bcc.
Cc, Bcccc, bccTexto ou lista.
SubjectsubjectNo Postmark o assunto é opcional. Na Postal é obrigatório quando não há template.
HtmlBody, TextBodyhtml, textComo no Postmark, um dos dois é obrigatório.
ReplyToreply_toTexto ou lista.
Headers (lista de {Name, Value})headers (objeto){"X-Pedido": "10482"}. Não dá para sobrescrever From, To, Subject, Message-ID e outros cabeçalhos de sistema.
Tag (um texto, até 1000 caracteres)tags: lista de {name, value}Até 50 por e-mail. Nome e valor só aceitam letras ASCII, números, _ e -, até 256 caracteres.
MetadatatagsPara valores curtos e simples, vire cada par uma tag; ela volta em data.tags nos webhooks. Dados com espaços, acentos ou mais longos ficam no seu banco, ligados ao id do e-mail.
TrackOpens, TrackLinksopen_tracking e click_tracking do domínioNo Postmark a escolha é de cada mensagem, inclusive HTML ou texto. Na Postal é ligado ou desligado no domínio e não existe campo no envio. Depende do plano.
Attachments: Name, Content, ContentType, ContentIDattachments: filename, content, content_type, content_idTambém aceita path, uma URL pública. Até 40 MB no total (no Postmark, 10 MB por e-mail, anexos incluídos). Anexos não valem no lote.
MessageStreamNão existeVeja o passo sobre Message Streams.
POST /email/batch: até 500 mensagensPOST /emails/batch: até 100 e-mailsSem anexos e sem agendamento. Tudo é validado antes: entram todos ou nenhum. No Postmark o lote responde 200 mesmo com falhas individuais.
POST /email/withTemplatePOST /emails com templatetemplate.id aceita o ID ou o alias. Veja o passo de templates.
POST /email/batchWithTemplatesPOST /emails/batch com template em cada itemCada item leva o próprio template.variables.
InlineCssSem campo igualA documentação da Postal não descreve essa opção.
Resposta: MessageID, ErrorCode, MessageResposta: {"id": "…"}O sucesso e o erro vêm pelo status HTTP. O erro tem statusCode, name e message: teste o name, não o texto.
Destinatário inativo (erro 406)Removido do envioA Postal tira os endereços suprimidos do envio e gera email.suppressed, sem erro.
POSTMARK_API_TEST no lugar do tokenEndereços de testeVocê manda para [email protected] e os outros endereços de teste. Não usa cota e gera webhooks reais. Depende do plano.
3

Recrie os templates

Os templates do Postmark aceitam blocos e repetições, como {{#each person}}, e o modelo (TemplateModel) é um objeto que pode ter outros objetos dentro. Os da Postal só substituem variáveis, no formato {{{NOME}}}, com três chaves, e cada valor precisa ser texto ou número. Quando o seu template depende de blocos, mova a lógica para o código: escolha entre templates ou monte o HTML você mesmo e mande em html.

  • Na Postal você edita um rascunho e publica; os envios usam sempre a última versão publicada, e as versões antigas podem ser restauradas.
  • A Postal também tem layouts, para reaproveitar desenhos de e-mail. Não os trate como idênticos aos layouts do Postmark: recrie e teste.
  • No HTML os valores são escapados. Uma variável sem valor e sem valor padrão (fallback_value) devolve erro 422.
Variáveis do template
- <p>Olá, {{name}}!</p>- <p>Seu pedido: {{order.id}}</p>+ <p>Olá, {{{NAME}}}!</p>+ <p>Seu pedido: {{{ORDER}}}</p>
Envio com template
- "TemplateAlias": "pedido-enviado",- "TemplateModel": { "name": "Ana", "order": { "id": 10482 } }+ "template": {+   "id": "pedido-enviado",+   "variables": { "NAME": "Ana", "ORDER": "10482" }+ }
4

Decida o que fazer com os Message Streams

No Postmark, um servidor tem streams: transacional (por padrão, outbound), de broadcasts e de entrada, até 10 no total, e as supressões são guardadas por stream. A Postal não tem esse objeto. Os três usos têm um caminho, mas nenhum é um campo no envio, e a separação de supressões não existe: a lista é única para o time.

  • Para isolar a reputação, use domínios ou subdomínios diferentes (um para o transacional, outro para as campanhas) e uma chave restrita a cada domínio.
  • A pausa por taxa alta de bounces ou reclamações vale para o time inteiro, não para um domínio ou uma tag. Veja os limites de reputação em Limites.
  • Se o seu uso depende de streams separados com supressões independentes, converse com a gente antes de migrar.
Stream transacionalO padrão, outbound
Envio normalPOST /emails, pela API ou pelo SMTP.
Stream de broadcastsNewsletters e promoções
CampanhasContatos, segmentos, tópicos e descadastro. O envio é uma campanha, não um campo do e-mail.
Stream de entradaE-mails recebidos
InboxesConversas na plataforma e o webhook email.received.
Supressões por streamUma lista para cada stream
Uma lista por timeUm bounce numa campanha também bloqueia o mesmo endereço nos e-mails transacionais.
Webhooks e estatísticas por streamSeparados por stream
Webhooks do timeMarque os envios com uma tag, como stream: transactional, e filtre pela tag no seu código.
5

Aponte o SMTP para a Postal

Se algum sistema envia por SMTP, troque servidor, usuário e senha e tire os cabeçalhos X-PM-*. As mensagens passam pelas mesmas regras da API (domínio verificado, cotas, lista de supressão) e aparecem em E-mails e Logs.

Nodemailer · antes e depois
- host: 'smtp.postmarkapp.com',- port: 587,- secure: false,- auth: { user: process.env.POSTMARK_SERVER_TOKEN, pass: process.env.POSTMARK_SERVER_TOKEN },+ host: 'smtp.postal.so',+ port: 465,+ secure: true,+ auth: { user: 'postal', pass: process.env.POSTAL_API_KEY },
PostmarkPostal
Servidorsmtp.postmarkapp.com (broadcasts: smtp-broadcasts.postmarkapp.com)smtp.postal.so
Portas25, 2525 ou 587, com STARTTLS465 com TLS (recomendada) ou 587 com STARTTLS. Se a rede bloquear, 2465 (TLS) ou 2587 (STARTTLS).
Usuário e senhaO token do servidor nos dois, ou chave de acesso e chave secreta de um token SMTPUsuário postal e uma chave de API (ps_…) como senha. Uma chave só de envio basta.
Opções por cabeçalhoX-PM-Message-Stream, X-PM-Tag e X-PM-Metadata-*Pelo SMTP não há tags, agendamento nem templates. Esses cabeçalhos seguiriam na mensagem como cabeçalhos comuns: tire-os.
HabilitarNas configurações do servidorO SMTP faz parte do plano. Sem ele a mensagem é recusada com 550 5.7.1.
6

Cadastre o webhook e confira a assinatura

Crie o endpoint na Postal (POST /webhooks, com URL HTTPS e a lista de eventos) e guarde o signing_secret. A mudança de fundo é a segurança: o Postmark não assina as entregas, e a Postal assina todas. Calcule a assinatura sobre o corpo exatamente como chegou, sem ler e reescrever o JSON.

  • O webhook-id é o mesmo em todas as tentativas: guarde os já processados para descartar duplicatas.
  • Responda 2xx logo e processe depois, numa fila.
  • Para testar sem enviar de verdade, mande e-mails para os endereços de teste ou use "Enviar evento de teste" no painel.
A Postal também avisa de email.sent, email.delivery_delayed, email.failed, email.suppressed, email.replied e outros.
Webhook do PostmarkEvento na PostalObservação
Deliveryemail.deliveredO servidor do destinatário aceitou a mensagem.
Bounceemail.bouncedEm data.bounce.type: Permanent, Transient ou Undetermined. O Postmark usa tipos como HardBounce e Transient. Bounce permanente coloca o endereço na lista de supressão.
Spam complaintemail.complainedO endereço entra na lista de supressão.
Openemail.openedPrecisa do rastreamento de aberturas ligado no domínio.
Clickemail.clickedTraz o link em data.click.link.
Subscription changesuppression.added e suppression.removedO mais próximo: avisam quando um endereço entra ou sai da lista de supressão. Confira o conteúdo antes de trocar.
Inboundemail.receivedChega quando uma inbox recebe uma mensagem. Receber no seu domínio é dos planos pagos.
PostmarkPostal
SegurançaSem assinatura: o Postmark não oferece verificação HMAC. A proteção é a autenticação básica na URL e a lista de IPs do Postmark.Cada entrega é assinada (Standard Webhooks): cabeçalhos webhook-id, webhook-timestamp e webhook-signature, com o whsec_… do endpoint. Quem confiava na URL passa a conferir a assinatura.
FormatoRecordType e os campos no nível de cimatype, created_at e data
Qual e-mail foiMessageIDdata.email_id, o mesmo id devolvido no envio
DestinatárioRecipient ou Email. Uma mensagem para várias pessoas gera vários eventos.data.to, uma lista. Trate como lista.
Tag e metadadosTag e Metadatadata.tags, um objeto nome → valor
Motivo do bounceDetails, Type e TypeCodedata.bounce (type, subType, message, diagnosticCode)
Resposta esperada200Qualquer 2xx em até 15 segundos
Novas tentativasRepete 5xx, 408, 429 e falhas de rede, em 1, 5, 10, 10, 10 e 15 minutos. Os outros 4xx descartam o evento.Toda resposta fora de 2xx conta como falha. 8 tentativas em cerca de 27 horas, com reenvio pelo painel ou por POST /webhooks/{id}/events/{event_id}/replay.
Tratamento · antes e depois
- if (req.body.RecordType === 'Bounce') {-   // req.body.Email, req.body.MessageID- }+ const event = verifyWebhook(req.body, req.headers, process.env.POSTAL_WEBHOOK_SECRET);+ if (event.type === 'email.bounced') {+   // event.data.to, event.data.email_id+ }
email.bounced · o que chega no seu servidor
{
  "type": "email.bounced",
  "created_at": "2026-10-24T13:02:15.004Z",
  "data": {
    "email_id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
    "to": ["[email protected]"],
    "subject": "Seu pedido #10482 saiu para entrega",
    "tags": { "stream": "transactional" },
    "bounce": {
      "type": "Permanent",
      "subType": "General",
      "message": "smtp; 550 5.1.1 user unknown"
    }
  }
}
200Responda 2xx em até 15 s
7

Leve as supressões

Faça isso antes do primeiro envio real, para não escrever a quem já pediu para sair nem repetir bounces. A Postal pausa os envios do time se os bounces permanentes passam do limite do nível da reputação da conta (3% numa conta nova) ou as reclamações passam de 0,3%; por isso a lista limpa importa. O exemplo lê a lista de um stream no Postmark e a envia à Postal em blocos de 100. Repita para cada stream e use uma chave de acesso total.

Node.js · ler no Postmark e enviar à Postal
- const base = 'https://api.postmarkapp.com/message-streams';- const dump = await fetch(`${base}/${stream}/suppressions/dump`, {-   headers: {-     Accept: 'application/json',-     'X-Postmark-Server-Token': process.env.POSTMARK_SERVER_TOKEN,-   },- }).then((r) => r.json());- const emails = dump.Suppressions.map((s) => s.EmailAddress);+ for (let i = 0; i < emails.length; i += 100) {+   await fetch('https://api.postal.so/suppressions/batch/add', {+     method: 'POST',+     headers: {+       Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,+       'Content-Type': 'application/json',+     },+     body: JSON.stringify({ emails: emails.slice(i, i + 100) }),+   });+ }
No PostmarkNa PostalComo levar
Supressões por stream (/message-streams/{stream_id}/suppressions)Uma lista por timeJunte as listas de todos os streams e envie para a Postal. Um endereço suprimido fica bloqueado para todos os envios do time.
HardBounceOrigem bounceO que você adiciona pela API entra como manual; as origens bounce e complaint são registradas pela Postal nos envios dela.
SpamComplaintOrigem complaintImporte para a lista de supressão e mantenha lá.
ManualSuppressionOrigem manualEntra pelo mesmo endpoint.
Até 50 endereços por chamadaAté 100 endereços por chamadaPOST /suppressions/batch/add, com chave de acesso total.
Origem Recipient (a própria pessoa)Contato com unsubscribed: trueQuem pediu para sair de e-mails de marketing não deve entrar na lista de supressão, porque ela bloqueia também os transacionais. Use POST /contacts/batch.
8

Vire o tráfego aos poucos

Os IDs são diferentes: o MessageID de um e-mail do Postmark não existe na Postal. Durante a virada, guarde de qual provedor veio cada e-mail e continue recebendo os webhooks do Postmark para o que saiu por lá. Mande cada fluxo por um provedor só, para não duplicar nem pausar tudo por causa de e-mails errados.

Antes de virar a chave

  • Domínio verificado na Postal, com os registros antigos ainda no lugar.
  • Chave só de envio, restrita ao domínio, fora do código e do repositório.
  • Decisão tomada sobre cada Message Stream: envio normal, campanha ou inbox.
  • Supressões de todos os streams importadas, e descadastros de marketing importados como contatos descadastrados.
  • Templates recriados com {{{VARIÁVEL}}}, publicados, e a lógica de blocos movida para o código.
  • Metadados que vão em tags renomeados para letras, números, _ e -.
  • Webhook criado, assinatura conferida com o corpo cru e resposta 2xx em até 15 segundos.
  • Testes feitos com [email protected] e [email protected].
  • Um fluxo por vez, olhando os bounces nos primeiros dias.

Perguntas comuns

A Postal tem Message Streams?

Não. A separação entre e-mail transacional e de marketing na Postal se faz com serviços diferentes (envio por API ou SMTP de um lado, campanhas e contatos de outro), com domínios separados e com tags. A lista de supressão e os webhooks são do time inteiro.

Posso manter o Postmark enquanto migro?

Pode, e é o mais seguro. Use as duas contas lado a lado e vire um fluxo de cada vez. Só tire os registros do Postmark quando nada mais sair por lá.

O histórico de eventos do Postmark vem junto?

Não. A Postal não importa e-mails nem eventos de outra plataforma, e guarda os seus por 30 dias. Exporte o que precisar antes de desligar o Postmark.

Vai migrar do Postmark?

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