Envio de e-mail
Cada chamada envia um e-mail, a partir de um modelo já criado no Workfuse. O envio sai pela conta de envio da instituição e passa pelo limite do aquecimento de IP dela, o mesmo das campanhas.
A URL completa é https://api.workfuse.com/public/v1/email/send. Este endpoint mora em
/email, não em /crm, mas usa a mesma chave do módulo CRM.
Antes de começar
- Um modelo salvo no editor. Crie o modelo em E-mails → Modelos e salve. Modelo que
nunca foi salvo no editor responde
422. - O slug do modelo. Copie na tela do modelo. O slug não muda quando o modelo é
renomeado, então a URL do seu webhook não quebra. O padrão é o nome em minúsculas, sem
acento, com hífen:
Boas-vindas Matrículaviraboas-vindas-matricula. - A conta de envio. A equipe Workfuse cadastra a conta de envio da instituição. Sem ela,
a resposta é
422. - Uma API key com o módulo CRM no escopo. Veja Autenticação. A organização também precisa ter o CRM habilitado.
Enviar um e-mail
POST /email/send · assíncrono
curl -X POST "https://api.workfuse.com/public/v1/email/send" \
-H "Authorization: Bearer wf_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"to": "ana.souza@exemplo.com",
"templateSlug": "boas-vindas-matricula",
"subject": "Bem-vinda, Ana",
"idempotencyKey": "matricula-88431",
"name": "Ana Souza",
"curso": "Engenharia Civil"
}'
O corpo é um JSON plano. As chaves reservadas são as da tabela; toda outra chave vira uma variável do modelo.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
to | string | sim | E-mail do destinatário. Tem que ser um e-mail válido. Espaços nas pontas são removidos e tudo vai para minúsculas. |
templateSlug | string | sim* | O slug do modelo, até 160 caracteres. Também vale na URL, como ?templateSlug=; o do corpo vence. |
templateId | string | sim* | O id do modelo, até 200 caracteres. Alternativa ao slug. |
subject | string | não | O assunto, até 998 caracteres. Sem ele, vale o assunto do modelo. Sem nenhum dos dois, a resposta é 422. |
idempotencyKey | string | não | Até 200 caracteres. Ver Idempotência. |
variables | object | não | Variáveis do modelo. Aceita o mesmo que as chaves soltas. |
* Informe templateSlug ou templateId: exatamente um. Os dois juntos, ou nenhum,
respondem 422. Isso inclui ?templateSlug= na URL com templateId no corpo: também é 422.
O to não tem limite próprio de tamanho: a API não corta nem recusa pelo comprimento, só
exige que seja um e-mail válido.
O slug é comparado exatamente como foi gravado e só procura na instituição da chave. Modelo
excluído responde 404. Excluir um modelo libera o slug: um modelo novo criado depois com o
mesmo slug passa a responder por ele.
Uma string vazia ("") em subject, idempotencyKey, templateSlug e templateId conta
como não informado. É assim que o HubSpot envia uma propriedade vazia.
Variáveis no modelo
No HTML do modelo, {{curso}} é trocado pelo valor enviado e {{curso|seu curso}} usa
seu curso quando o valor vem vazio, null ou ausente.
- Só valores simples. Cada variável é
string,number,booleanounull. Objeto ou lista, em qualquer chave, responde400. - Todo valor é escapado como HTML. Não dá para mandar marcação pela variável.
variablesvence a chave solta. Secursovier solto e dentro devariables, vale o devariables.name,email,phone,cpf,birth_dateegendertambém são lidos das variáveis.{{email}}sem variável usa oto.- O assunto não resolve variável. O texto de
subjectvai como veio. - Variável ausente sem texto padrão vira vazio.
{{curso}}sem valor e sem|padrãosome do e-mail, sem erro. - Sem limite de variáveis. A API não limita quantas variáveis vêm nem o tamanho de cada valor.
Link de descadastro
Todo e-mail sai com o descadastro, sem nada no modelo:
- No HTML, um rodapé com o link Descadastrar entra antes do último
</body>(ou no fim, se não houver</body>). Na versão em texto, o link vai na última linha. - O e-mail leva os cabeçalhos
List-UnsubscribeeList-Unsubscribe-Post(descadastro em um clique nos clientes de e-mail).
Quem se descadastra passa a ser suprimido: os próximos envios para o endereço recebem 202 e
não saem.
Resposta 202
{
"externalMessageId": "clzd8p3r40004s6vp6c2n5t7b"
}
O 202 quer dizer aceito e na fila, não enviado.
- Endereço suprimido (descadastro, bounce ou denúncia de spam) também recebe
202, e o e-mail não sai. - Depois do
202, o envio ainda pode falhar sem aviso: conta de envio removida, modelo excluído, recusa do SendGrid ou retentativas esgotadas. - O modelo é lido na hora do envio. Editar o modelo entre o
202e o envio muda o que sai, e um modelo excluído nesse meio não é enviado. - O resultado (entregue, bounce, spam) não volta por esta API. A instituição acompanha na
aba Entregabilidade. Não há consulta por
externalMessageIdnem webhook de status para quem chama.
Retenção dos dados
Os dados pessoais de um envio encerrado (enviado, falho ou suprimido) são apagados 90 dias depois da data prevista de envio: o endereço, o assunto, as variáveis e a mensagem de erro. O status, as datas e os eventos do provedor ficam, então relatórios e o limite do aquecimento não mudam.
Idempotência — idempotencyKey
Repetir o mesmo idempotencyKey devolve 202 com o mesmo externalMessageId e não
enfileira outro e-mail, mesmo que o modelo tenha mudado ou sido excluído depois. O escopo é a
instituição da chave: a mesma chave em outra instituição é outro envio.
- Chave reaproveitada com outro pedido é
409. A mesma chave comto,templateId,templateSlug,subjectou variáveis diferentes responde409EMAIL_SEND_IDEMPOTENCY_KEY_REUSEDe nada é enfileirado. Maiúsculas notoe a ordem das variáveis não contam como diferença. Envios gravados antes desta regra continuam respondendo202com o id antigo. - A repetição pula as validações. Modelo, assunto e conta de envio não são checados:
a resposta é
202mesmo que um pedido novo igual falhasse com404ou422. - Pedidos simultâneos com a mesma chave gravam um envio só e devolvem o mesmo
externalMessageId.
Gere uma chave por ocorrência de envio: o mesmo valor só nas retentativas desse envio. Record ID, nome do workflow e slug do modelo sozinhos se repetem quando o mesmo contato recebe o modelo de novo (reinscrição, outro evento), e o segundo envio é tratado como repetição e não sai. Inclua algo que muda a cada ocorrência, como o id do evento ou a data da inscrição.
Sem a chave, não há proteção contra duplicata depois de um timeout.
Quando o limite do aquecimento é atingido
A API nunca recusa um envio pelo limite do aquecimento. Ela responde 202 e o e-mail espera. O único limite que recusa é o de pedidos por minuto, com 429 (ver Erros).
- Acima do limite da hora, o e-mail sai na hora seguinte. Acima do limite do dia, sai
no dia seguinte. O relógio é o de
America/Sao_Paulo. - Com a rampa pausada por denúncia de spam, o e-mail é reavaliado a cada hora.
- Só conta no limite o e-mail que foi enviado.
- A ordem de saída dos e-mails que esperam não é garantida.
- Depois de 30 dias esperando, o envio desiste e não sai.
- Campanhas e API da mesma instituição dividem o mesmo limite.
Erros
O formato de cada envelope está em Erros.
| HTTP | errorCode | Quando |
|---|---|---|
400 | — | Corpo malformado, to inválido, ou variável que não é valor simples. |
401 | API_KEY_MISSING, API_KEY_INVALID | Chave ausente ou inválida. Com token de conta de serviço, SERVICE_ACCOUNT_TOKEN_INVALID. |
403 | MODULE_NOT_IN_KEY_SCOPE, CAPABILITY_NOT_ENABLED | A chave não tem o módulo CRM, ou a organização não tem o CRM habilitado. Com conta de serviço, PERMISSION_DENIED. |
404 | EMAIL_TEMPLATE_NOT_FOUND | Slug ou id inexistente, excluído ou de outra instituição. |
409 | EMAIL_SEND_IDEMPOTENCY_KEY_REUSED | idempotencyKey já usada nesta instituição com outro pedido. Não retente: gere outra chave. |
422 | EMAIL_SEND_TEMPLATE_SELECTOR_INVALID | templateSlug e templateId juntos, ou nenhum. |
422 | EMAIL_TEMPLATE_NOT_RENDERED | O modelo nunca foi salvo no editor. |
422 | EMAIL_SEND_SUBJECT_MISSING | Sem subject no pedido e no modelo. |
422 | EMAIL_SENDER_ACCOUNT_NOT_CONFIGURED | A instituição ainda não tem conta de envio. |
429 | SERVICE_ACCOUNT_RATE_LIMITED | Só com token de conta de serviço: 120 pedidos por minuto. Respeite o Retry-After. |
429 | EMAIL_SEND_RATE_LIMITED | Só com API key wf_: 120 envios por minuto por chave (padrão). Respeite o Retry-After. |
5xx | — | Falha nossa e transitória. |
Limite de pedidos por minuto
Com API key wf_, o limite padrão é 120 envios por minuto por chave, numa janela de 1
minuto. A equipe Workfuse ajusta esse valor no servidor pela variável de ambiente
EMAIL_SEND_API_KEY_RATE_LIMIT_PER_MINUTE (um número maior que zero, arredondado para baixo; ausente, zero, negativo ou não numérico volta para 120).
As respostas desse limite trazem os cabeçalhos RateLimit-Limit, RateLimit-Remaining e
RateLimit-Reset (segundos até a janela virar). O 429 traz também o Retry-After.
Retentativa
Retente 5xx e 429, com o mesmo idempotencyKey. Não retente 4xx: é erro de
configuração, e o mesmo pedido volta a falhar.
Se o envio já tinha sido gravado antes do 5xx, ele não se perde, e o reenvio com a mesma
chave devolve 202 com o mesmo externalMessageId, sem duplicar:
- Com retentativa. Se a falha foi ao colocar o envio na fila, o reenvio com a mesma chave o coloca na fila na hora.
- Sem retentativa. Uma rotina a cada 5 minutos recoloca na fila o envio parado há mais de 2 horas. Se ele continuar parado, a rotina tenta de novo com espera crescente: por volta de 2 h, 6 h, 14 h e 30 h depois do pedido, e daí uma vez por dia. Passado o prazo máximo de espera do aquecimento mais 1 dia, o envio desiste e não sai.
Configurar no HubSpot
Para disparar o e-mail de um workflow, use a ação Send a webhook. Ela exige o Data Hub Professional ou Enterprise.
- Opcional: acima da ação, um random split, para soltar só um percentual dos registros enquanto a rampa do aquecimento cresce.
- Adicione a ação Data ops → Send a webhook e escolha o método POST.
- Na URL, fixe o modelo pelo slug:
https://api.workfuse.com/public/v1/email/send?templateSlug=boas-vindas-matricula. Uma ação envia um modelo só. - Em Authentication type, escolha API key: nome
Authorization, local Request header, e o segredo com o valorBearer wf_sua_chave_aqui, com a palavraBearere o espaço antes da chave. - Em Customize request body, monte o corpo:
to= a propriedade Email;idempotencyKey= a propriedade que você montou com Format data (concat(...)grava num campo de texto que pode ir no corpo), juntando o Record ID a algo que muda a cada ocorrência, como explicado em Idempotência;- uma chave por variável do modelo, com a propriedade do contato;
subjectcom um valor estático, se quiser trocar o assunto do modelo.
- Ligue Configure rate limit. Ele segura a ação e as seguintes do workflow, e a fila não acumula além do limite do dia.
- Rode Test action. Um
202comexternalMessageIdé o esperado.
O HubSpot dispara uma requisição por registro: cada contato que entra no workflow gera
um POST.
Nunca use "Include all properties". O corpo que o HubSpot monta traz listas e objetos
aninhados, e a API responde 400 a valor que não é simples. O HubSpot não retenta 4xx,
então o envio se perde.
Propriedade vazia chega como "" e cai no texto padrão da variável, se o modelo tiver um.
Retentativa do HubSpot
O HubSpot retenta por até 3 dias, a primeira vez 1 minuto depois, com intervalo máximo de 8
horas. Não retenta 4xx, exceto 429, e nesse caso respeita o Retry-After. Isso casa
com a regra da seção de erros: 5xx e 429 voltam, e o idempotencyKey evita o
envio em duplicata.
Comportamento medido em knowledge.hubspot.com em 16/09/2026.