MillionSend Docs

Migrar do Resend

Um comando move sua conta do Resend; duas linhas de ambiente movem seu código — o formato da API é idêntico.

A API REST do MillionSend é compatível com a do Resend: mesmos endpoints, mesmos formatos de requisição e resposta. Migrar é uma mudança de configuração, não uma reescrita. Os dados da conta — contatos, segmentos, tópicos, templates, webhooks, domínios, supressões — vêm com um comando.

Passe para um agente

A migração inteira — inventário, movimentação da conta, mudanças de código, DNS, virada — está escrita como um único prompt que um agente pode seguir de ponta a ponta, incluindo as proteções (Resend somente leitura, chaves nunca em arquivos, perguntar antes de aplicar).

Ou aponte o agente para ele: https://docs.millionsend.com/pt-BR/prompts/migrate-from-resend.md.

1. Mova sua conta

Crie uma chave de API ms_ com acesso total em Chaves de API (MillionSend Cloud, ou sua própria instância auto-hospedada) e rode, na sua máquina:

npx @millionsend/cli migrate --from resend

Ele pede sua chave do Resend (acesso total) e sua chave do MillionSend, lê sua conta no Resend, mostra um plano, aguarda a confirmação, aplica e imprime um resumo. Auto-hospedado, informe a URL da API da sua instância:

npx @millionsend/cli migrate --from resend --to-url https://api.sua-instancia

O que ele faz:

  • O Resend é apenas lido. Toda requisição ao Resend é um GET a um endpoint documentado; nada lá é criado, alterado ou excluído. A CLI mostra ao conectar o limite de taxa que o Resend informa e se mantém abaixo dele: 8 requisições por segundo por padrão (o limite da equipe no Resend é 10, compartilhado com o seu envio em produção), reduzindo o ritmo conforme os cabeçalhos ratelimit-* e a cada 429. --rps muda o ritmo.
  • Suas chaves nunca saem da sua máquina. A ferramenta fala apenas com api.resend.com e com a sua API do MillionSend. As chaves ficam em memória durante a execução, nunca são gravadas em arquivo e são mascaradas em toda linha de log. Sem telemetria, sem verificação de atualização.
  • Virada primeiro, enriquecimento depois. A passagem 1 cria propriedades, tópicos, segmentos, domínios, webhooks, templates, transmissões e supressões e faz upsert dos contatos com suas associações a segmentos e o unsubscribed. Ela termina em minutos, e a CLI então imprime Cutover ready com os registros DNS e a linha do RESEND_BASE_URL: o envio transacional já pode mudar nesse ponto. O enriquecimento — só quando a conta usa tópicos ou propriedades de contato — lê então cada contato uma vez por faceta, primeiro as inscrições em tópicos (para os opt-outs chegarem antes das propriedades), depois as propriedades, com ritmo e tempo restante ao vivo. Segure envios por tópico e transmissões até ele terminar. As duas passagens retomam de onde pararam após um Ctrl-C e uma nova execução, e --skip enrichment as deixa de fora.
  • Rode de novo antes da virada. Cada execução é um diff: linhas existentes são atualizadas quando diferem e deixadas como estão quando são iguais; contatos passam por upsert pelo e-mail. Rode o mesmo comando de novo logo antes de virar o tráfego e os contatos que chegaram nesse meio-tempo vêm junto. Uma nova execução lê todos os contatos de novo, então custa o tempo inteiro de enriquecimento; --only enrichment roda só as duas passagens sobre contatos que já estão no destino, e --only properties,enrichment roda só a passagem de propriedades. Contas migradas com a CLI 0.1.x não receberam valores de propriedades (o formato da API foi lido errado); esse único comando os preenche. Um contato que surgiu no Resend desde a última execução é criado por essa passagem com o unsubscribed e os nomes, e fica registrado para o rollback como qualquer outro. O checklist de virada só é impresso quando contatos, domínios e supressões fazem parte da execução.

Antes de uma migração grande

O enriquecimento domina: dois GETs por contato contra um limite compartilhado com o envio que o seu app faz na mesma equipe do Resend. A estimativa que o plano imprime segue de contatos × facetas ÷ ritmo:

ContatosFacetasA 8 req/sA 10 req/sA 50 req/s
36.685tópicos + propriedadescerca de 2 h 30 mincerca de 2 hcerca de 25 min
160.000tópicos + propriedadescerca de 11 hcerca de 9 hcerca de 1 h 50 min
160.000só tópicoscerca de 5 h 30 mincerca de 4 h 30 mincerca de 55 min

Três coisas encurtam isso:

  • Peça ao Resend um aumento temporário. A documentação do Resend diz que o limite da equipe "pode ser aumentado para remetentes confiáveis sob pedido" (Settings → Usage mostra o atual). Passe o ritmo concedido explicitamente — --rps 50 — a CLI aceita valores acima de 10 e avisa quando o ritmo passa do limite que detectou.
  • Rode fora do horário de pico. O limite é por equipe, então o enriquecimento compete com os envios da sua produção; os 429 caem nos dois lados. Uma segunda chave de API não ajuda.
  • Deixe folga. Quando a CLI detecta um limite acima de 10 e --rps não foi passado, ela usa o limite menos 2 para o seu app continuar enviando.
RecursoO que vem
ContatosE-mail, nomes, unsubscribed, propriedades, inscrições em tópicos, associações a segmentos. Descadastros são preservados; ninguém é reinscrito.
Segmentos, tópicos, propriedadesCasados por nome / nome / chave: criados quando faltam, atualizados quando diferem.
TemplatesNome, alias, assunto, html, texto. from, reply_to e variables não podem ser armazenados — listados como passos manuais.
WebhooksEndpoint e eventos. O segredo de assinatura é copiado, então o receptor que você já roda continua verificando (as entregas carregam os headers svix-*). --fresh-webhook-secrets gera segredos novos. Eventos que o MillionSend também emite são levados junto — todos os onze tipos email.*, contact.created, contact.updated e contact.deleted, suppression.added e suppression.removed, e os doze tipos inbox.* que o MillionSend envia; os demais (contact.topics.updated, domain.*, topic.*, inbox.thread.assigned, inbox.thread.unassigned) são descartados por webhook e listados. email.scheduled, email.failed e email.suppressed só são levados para uma instância que emite esses eventos (o GET /openapi.json dela lista os três). Uma instância mais antiga recusa esses webhooks com 422: para migrar para ela, use npx @millionsend/cli@0.2.3, que descarta os três junto com suppression.added e suppression.removed e lista os cinco; depois, adicione os dois de novo ao webhook, pelo painel ou com PATCH /webhooks/{id} enviando a lista events completa (o PATCH substitui a lista).
SupressõesBounces, reclamações e entradas manuais, com sua origem.
DomíniosCriados com return path e configurações de rastreamento, na única região do SES atendida pela sua instância do MillionSend (MillionSend Cloud: sa-east-1) — a região do Resend não é levada junto. No MillionSend Cloud, as opções de rastreamento só vêm junto com um subdomínio de rastreamento; sem ele, o relatório as lista para você configurar no painel. Os registros DNS precisam ser adicionados de novo — veja o passo 3.
TransmissõesRascunhos e transmissões agendadas entram como rascunhos. As já enviadas são puladas, a menos que --include-sent.

O que não vem: chaves de API (o Resend expõe apenas os nomes — o relatório as lista como pendência), registros DKIM/DNS (as chaves são por provedor), caixas de entrada com as conversas e os rascunhos delas (crie de novo; veja o passo 3) e o histórico de e-mails, enviados ou recebidos. Audiências, descontinuadas no Resend, são puladas — segmentos as cobrem.

Flags, variáveis de ambiente, arquivos, códigos de saída e uso em CI estão na referência da CLI.

2. Aponte seu código existente para o MillionSend

Os SDKs oficiais do Resend respeitam RESEND_BASE_URL, então a migração são duas linhas de ambiente — nenhuma mudança de código:

RESEND_API_KEY=ms_...
RESEND_BASE_URL=https://api.millionsend.com

Auto-hospedado, use a origem da API da sua instância.

Cinco detalhes que diferem do que uma integração com o Resend pode assumir:

  • Os campos de remetente e destinatário aceitam exatamente uma caixa postal cada, nas formas da RFC 5322 ada@example.com, Ada <ada@example.com> ou "Ada, Inc." <ada@example.com>. Um nome de exibição com vírgula precisa estar entre aspas; sem elas, é lido como dois endereços e o envio é rejeitado com 422. O Resend aceita a forma sem aspas.

  • PATCH /contacts/{id}/topics recebe um array JSON puro de entradas { "id": "<topic-id>", "subscription": "opt_in" | "opt_out" }, não um objeto em volta dele; GET /contacts/{id}/topics lê de volta a escolha efetiva por tópico.

  • Não existe POST /contacts/imports (CSV). Contatos em massa vão por POST /contacts/batch?on_conflict=upsert em JSON, até 1.000 por chamada, com x-batch-validation: permissive para manter as linhas válidas quando algumas falham.

  • Um destinatário cujo domínio nunca recebe e-mail, como x@example.com, x@foo.test ou x@gmail.con, é recusado com 422 validation_error, e um contato novo num endereço assim também: a migração lista esses contatos entre as falhas em vez de trazê-los. Veja Erros.

  • Um envio em que todos os destinatários de to estão suprimidos é recusado com 422 all_recipients_suppressed; o Resend aceita esse envio, registra o e-mail como suppressed e envia o evento email.suppressed. Aqui, o POST /emails também guarda um e-mail suppressed só com os metadados e envia o evento email.suppressed, mas responde 422 em vez de 200. A migração copia os bounces, as reclamações e as entradas manuais do Resend, então o seu código pode estar enviando para endereços que já estão na lista: trate esse erro como "não enviado", e não como uma falha para tentar de novo. No POST /emails/batch, um item assim rejeita o lote inteiro; com x-batch-validation: permissive, os outros itens são aceitos e esse item aparece em errors. Veja Supressões.

Prefere usar um SDK oficial? Os SDKs do MillionSend cobrem nove linguagens com a mesma superfície:

import { MillionSend } from "millionsend";

const ms = new MillionSend("ms_...", { baseUrl: "https://api.millionsend.com" });

3. Conclua o que a CLI lista

O resumo termina com um checklist. Três itens sempre estão nele:

  • Adicione os registros DNS de cada domínio. O MillionSend usa um par de chaves DKIM próprio, então os registros são novos mesmo para um domínio que já envia pelo Resend. O relatório imprime uma tabela pronta para copiar com os registros por domínio (também em Domínios no painel). Os dois provedores podem ficar verificados lado a lado durante a migração.
  • Defina RESEND_BASE_URL (passo 2) em todo ambiente que envia.
  • Crie as chaves de API — uma por nome que o relatório lista (por exemplo prod, staging) em Chaves de API.

Mais dois aparecem quando se aplicam: valores de from / reply_to de templates para definir por envio, e tipos de evento de webhook que o MillionSend não emite. Corpos de transmissão não precisam de mudança: {{{RESEND_UNSUBSCRIBE_URL}}} é um alias suportado de {{{UNSUBSCRIBE_URL}}}.

Também recebe e-mails pelo Resend? Ative o recebimento do domínio aqui depois que ele estiver verificado (Ativar recebimento, na página dele) e escolha quais endereços recebem. O Resend recebe em qualquer endereço do domínio; aqui isso é o Pega-tudo, na mesma página (um plano pago no MillionSend Cloud), ou então uma caixa de entrada por endereço. Depois troque o registro MX do domínio, do que o Resend passou para mx.millionsend-dns.com (na auto-hospedagem: o RECEIVING_MX_HOST da sua instância). O e-mail vai para o registro MX com o menor número de prioridade, então essa troca é a virada; o que o Resend já recebeu fica lá. Código que chama resend.emails.receiving.* e handlers de email.received não precisam de mudança. Veja Recebimento.

Usa o pré-lançamento das Inboxes do Resend? Aponte o resend@6.32.1-preview-inboxes.6 para o MillionSend como qualquer outro SDK do Resend, e crie de novo cada caixa com o mesmo endereço (POST /inboxes, ou Caixas de entrada → Criar caixa de entrada); as conversas e os rascunhos ficam no Resend. Uma caixa criada por um SDK do Resend é uma caixa de agente, com as proteções do MillionSend ligadas: o e-mail de remetentes que o próprio domínio não garante, por exemplo, espera na quarentena. Crie as caixas lidas por pessoas como kind: "human", no painel ou por HTTP direto. O forwarding: true dá um receiving_address no domínio compartilhado das caixas da instância, então exige um. Veja Caixas de entrada → Compatibilidade com o Resend.

Envie um e-mail pela nova URL base e veja-o chegar a Entregue na página de E-mails — a migração é isso.


Resend é uma marca registrada da Plus Five Five, Inc. O MillionSend não é afiliado nem endossado pelo Resend.

Nesta página