Campanhas
Uma campanha (broadcast) envia uma mensagem a todos os contatos inscritos de um público. O SpacePost a divide em um e-mail personalizado por contato, adiciona os links de cancelamento de inscrição e os cabeçalhos List-Unsubscribe, ignora os endereços suprimidos e acompanha tudo como qualquer outro e-mail.
Ciclo de vida
Seção intitulada “Ciclo de vida”| Status | Significado |
|---|---|
draft |
Criada, editável, não enviada. |
queued |
send foi chamado; aguardando o horário de início (imediatamente ou scheduled_at). |
sending |
Sendo distribuída aos contatos. |
sent |
Todos os e-mails foram aceitos para entrega. sent_at está definido. |
failed |
A distribuição foi interrompida, por exemplo porque uma cota foi atingida ou a equipe foi pausada. |
canceled |
Cancelada antes do envio. |
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);const audienceId = "0192e1c4-0000-7000-8000-0000000000a1";
// 1. Create a draftconst { data: draft, error } = await spacepost.broadcasts.create({ audienceId, name: "October newsletter", from: "Acme <news@acme.com>", replyTo: "support@acme.com", subject: "What's new at Acme, {{{FIRST_NAME|friend}}}", previewText: "Three things we shipped this month", html: ` <h1>Hi {{{FIRST_NAME|there}}},</h1> <p>Here is what is new.</p> <p><a href="{{{SPACEPOST_UNSUBSCRIBE_URL}}}">Unsubscribe</a></p> `,});if (error) throw new Error(error.message);
// 2. Send it now, or schedule itconst { error: sendError } = await spacepost.broadcasts.send(draft.id, { scheduledAt: "in 1 hour" });if (sendError) throw new Error(sendError.message);
// 3. Check progressconst { data: broadcast } = await spacepost.broadcasts.get(draft.id);console.log(broadcast?.status); // "queued" | "sending" | "sent" | "failed"Criar uma campanha
Seção intitulada “Criar uma campanha”POST /broadcasts cria um rascunho e retorna { "id": "..." }.
audience_idstring (uuid)obrigatórioO público para o qual enviar.
fromstringobrigatórioRemetente em um domínio verificado.
subjectstringobrigatórioDe 1 a 998 caracteres. Há suporte a placeholders.
htmlstringopcionalCorpo em HTML. Informe html, text ou ambos antes de enviar.
textstringopcionalCorpo em texto simples.
reply_tostring | string[]opcionalAté 50 endereços.
preview_textstringopcionalAté 200 caracteres. Exibido depois do assunto nas caixas de entrada; inserido como um preheader oculto.
namestringopcionalAté 100 caracteres. Um rótulo interno.
Atualizar e excluir
Seção intitulada “Atualizar e excluir”PATCH /broadcasts/{id} aceita os mesmos campos (todos opcionais) e funciona somente em rascunhos. DELETE /broadcasts/{id} funciona em rascunhos e em campanhas agendadas para o futuro.
# nowcurl -X POST https://api.spacepost.dev/broadcasts/$ID/send -H "Authorization: Bearer $SPACEPOST_API_KEY"
# scheduled (ISO or natural language, up to 30 days ahead)curl -X POST https://api.spacepost.dev/broadcasts/$ID/send \ -H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \ -d '{ "scheduled_at": "2026-11-01T09:00:00Z" }'await spacepost.broadcasts.send(id); // nowawait spacepost.broadcasts.send(id, { scheduledAt: "in 1 hour" }); // laterSó podem ser enviados rascunhos com corpo html ou text, a partir de um domínio verificado. A resposta é { "id": "..." }.
Verificações de prontidão
Seção intitulada “Verificações de prontidão”Antes de enviar, GET /broadcasts/{id}/readiness retorna uma checklist de entregabilidade (o painel mostra a mesma lista no editor). É uma extensão do SpacePost e não faz parte da API do Resend.
{ "ready": true, "checks": [{ "id": "html_size", "level": "warn", "title": "Gmail will clip this message", "detail": "..." }] }| Verificação | Nível | Quando |
|---|---|---|
sender_domain |
error | O domínio de from está ausente ou não está verificado. |
dmarc |
warn | Não há registro v=DMARC1 em _dmarc.<domain>, ou a consulta falhou. p=none passa. |
subject |
error / warn | Error quando vazio. Warning para TUDO EM MAIÚSCULAS, !!! ou $$$, ou mais de 90 caracteres. |
html_size |
warn | HTML acima de 102 KB, que o Gmail corta. |
images |
warn | Apenas imagens, ou quatro ou mais imagens com pouco texto. |
shorteners |
warn | Links por bit.ly, tinyurl.com, t.co e similares. |
spam_phrases |
warn | Expressões típicas de spam no assunto ou no corpo. |
unsubscribe |
ok | Um rodapé de cancelamento de inscrição é adicionado automaticamente quando o corpo não tem o placeholder, então isso nunca bloqueia. |
audience |
error | Não há contatos inscritos. |
role_addresses |
warn | Mais de 20% dos contatos são endereços como info@ ou admin@. |
ready é false quando qualquer verificação tem o nível error. O envio então falha com um validation_error (422) que lista o que corrigir. Os warnings nunca bloqueiam um envio.
Os e-mails de campanha também levam os cabeçalhos List-Id e Feedback-ID (<broadcast id>:<team id>:broadcast:spacepost) para que o Gmail Postmaster Tools possa atribuir as reclamações a uma campanha. Os e-mails transacionais não os levam.
O que o destinatário recebe
Seção intitulada “O que o destinatário recebe”- A personalização é aplicada a
subject,htmletext(Personalização). - Cancelamento de inscrição: se o seu corpo não tiver o placeholder
{{{SPACEPOST_UNSUBSCRIBE_URL}}}/{{{RESEND_UNSUBSCRIBE_URL}}}, um rodapé de cancelamento de inscrição é anexado automaticamente. Os cabeçalhosList-UnsubscribeeList-Unsubscribe-Post: List-Unsubscribe=One-Clicksão sempre adicionados. - O Texto de prévia vira um preheader oculto no topo do HTML.
- Os contatos que cancelaram a inscrição e os endereços da sua lista de supressão são ignorados.
Cota e limites
Seção intitulada “Cota e limites”Cada destinatário conta como um e-mail na cota do seu plano, e o sandbox de 100 por dia vale para contas novas. Se a cota se esgotar durante o envio, a campanha termina como failed; os e-mails já criados continuam sendo entregues. A distribuição pode ser retomada se um worker for reiniciado.
Cada e-mail de campanha é um e-mail normal: aparece em GET /emails, leva broadcast_id nos payloads de webhook e pode ser rejeitado ou aberto.