TF Fiscal
Documentation

Invoice e-mail

Send authorized NF-e / NFS-e / CT-e / DC-e to the customer by e-mail: templates and variables, body HTML and images, attachments and download links, sender rules, states, idempotency and 409, webhook events, error model and an integration checklist.

Capabilities and onboarding flow

StepEndpointNotes
1 Save templatePOST /openapi/v2/emails/templatesIntegrator-level, shared by every company; several per document type allowed, padrao marks the default
2 Register callbackPOST /openapi/v1/webhooksList email.sent and email.failed in eventos; without a subscription only the query is left
3 SendPOST /openapi/v2/empresas/{empresaId}/emailsReturns the emailId at acceptance; delivery is asynchronous
4 QueryGET /openapi/v2/empresas/{empresaId}/emails/{emailId}Status, wait reason, failure reason and what was sent
SupportGET .../emails/templates · GET .../emails/templates/{templateId}Template list and detail; the detail carries the list of allowed variables

Authentication is the same as for the other endpoints: token / timestamp / sign headers, see Authentication. The e-mail only goes out when you call the send endpoint; nothing is sent automatically on authorization.

Templates and sending

  • A template belongs to the integrator, not to a company; any registered company uses any template.
  • Each template has a fixed document type (tipoDocumento) and language (idioma). There may be several per type, for example one per language.
  • Each document type has at most one default template (padrao = true), regardless of language. A send without templateId uses it; without a default it returns 10021012. To use another template, pass templateId when sending.
  • Marking a template as default removes the previous default of the same type; a disabled template (ativo = false) cannot be used for sending nor be the default.
  • Saving replaces the whole template: with templateId, resend every field; an omitted optional field goes back to its default.
  • At send time the template must be enabled and of the document type, otherwise 10021002.

Variables

Write {{nome}} in the subject or the body; the name starts with a letter and accepts letters, digits, underscore and dot, with optional spaces inside the braces. At save time each name is checked against the list of the document type; an unknown name returns 10021003. At render time a variable without a value becomes empty text. In the body the value is HTML-escaped; in the subject it is not.

Common to the four types:

VariableMeaning
numeroDocument number
serieSeries
chaveAcessoAccess key, 44 or 50 digits
dataEmissaoIssue time, São Paulo time zone
valorTotalTotal amount: NF-e total, NFS-e service amount, CT-e total service value, DC-e declared value
emitente.nomeIssuer company name
emitente.cnpjIssuer CNPJ
cliente.nomeName of the invoice recipient: NF-e buyer, NFS-e and CT-e service taker, DC-e recipient
cliente.documentoRecipient document, CPF or CNPJ
linkPdfPDF download link
linkXmlAuthorized XML download link; empty if the XML is not archived yet

Type-specific:

TypeVariableMeaning
NFSecodigoVerificacaoNFS-e verification code
NFSemunicipioIssuer municipality name
NFSedescricaoServicoService description
NFSevalorIssISS amount
CTeremetente.nomeCargo sender name
CTedestinatario.nomeCargo recipient name
CTevalorFreteTotal freight value
DCemarketplace.siteMarketplace site, from the issuer DC-e profile

Amounts and dates follow the template idioma: pt / es use 1.234,56 and dd/MM/yyyy HH:mm; en / zh use 1,234.56 and yyyy-MM-dd HH:mm. The time is always São Paulo time. The variaveisDisponiveis field of Get template is the full list for the type.

Body HTML and images

The body is refused, not cleaned: anything outside the list returns 10021004 at save time, with its position. Allowed tags: p, div, span, br, hr, h1 to h6, strong, b, em, i, u, s, ul, ol, li, blockquote, table, thead, tbody, tr, th, td, a, img; allowed attributes: style, href, src, alt, width, height, align, colspan, rowspan. style must not contain url(, expression(, position: or behavior:; href only accepts https:, mailto:, {{linkPdf}} or {{linkXml}}; the body is limited to 64 KB.

Images only through an https address. The platform has no image upload; host the logo at a public address of your own. An http image is flagged as mixed content by mail clients and an inline data: image does not show in some webmails; both are refused at save time. A complete example with logo and buttons is in Save e-mail template.

  • enviarPdf = true attaches the PDF voucher (DANFE / DANFSe / DACTE / DACE) and enviarXml = true attaches the authorized XML; the files are named {tipo}-{chave}.pdf / .xml.
  • If the voucher or the XML is not available yet, the e-mail does not go out incomplete: it stays in Aguardando (motivoEspera = pdf or xml), rechecked every 30 seconds for up to 120 minutes; then it becomes Falha.
  • Even without attachments the body may carry the {{linkPdf}} / {{linkXml}} links; their validity follows the integrator file-link setting, 7 days by default.

Sender and recipients

  • The sender address is the platform address; your own e-mail or domain cannot be used. The display name is the issuer legal name and the reply-to is the company registered e-mail, so customer replies reach the company.
  • destinatarios accepts up to 5 addresses; omitted, it uses the customer e-mail given at issuance, which only NF-e and NFS-e carry; CT-e and DC-e require destinatarios. With none at all, 10021013.
  • Duplicate addresses are merged and the domain is lowercased; destinatarios in the acceptance and in the query is the effective, normalized list.

States

text
Aguardando ──► Enfileirado ──► Enviando ──┬──► Enviado (terminal)
└──► Falha (terminal)
StatusMeaningTerminal
AguardandoAccepted, waiting for document data; motivoEspera = chaveAcesso / pdf / xmlNo
EnfileiradoQueued, data completeNo
EnviandoRendering and sendingNo
EnviadoAccepted by the mail serviceYes
FalhaFinal failure, motivoFalha explainsYes

Aguardando only exists while the key (the 50-digit key of a São Paulo NFS-e comes back later through the national environment), the PDF or the XML is missing, and it does not consume attempts. A failure on the way out is retried with growing backoff, up to 5 attempts; exhausted, it becomes Falha. Other causes of Falha: document no longer authorized, template disabled after acceptance, voucher that cannot be issued.

Idempotency and 409

  • id is the idempotency key, unique within the company. Repeating the same id returns the original acceptance without creating another e-mail, even with a different body; the status in the response is the current state.
  • While the previous e-mail of the same document (same company, tipoDocumento and documentoId) has not reached a terminal state, a send with another id returns HTTP 409 and 10021015, with the emailId and status of the in-flight e-mail in the message. Wait for Enviado / Falha through the callback or the query, then resend with a new id.

Webhook events

List email.sent and email.failed in eventos when registering the webhook; both are only delivered to applications that subscribe to them explicitly. The payload is the event envelope, with data fields in snake_case:

json
{
"version": "1.0",
"event_id": "1950000000000006",
"event_type": "email.sent",
"occurred_at": "2026-09-28T12:00:31Z",
"data": {
"email_id": "2103160000000000001",
"id": "email-pedido-8842-1",
"tipo_documento": "NFe",
"documento_id": "pedido-8842",
"chave": "35241204893402000113650010000117691017244265",
"destinatarios": ["comprador@exemplo.com"],
"status": "Enviado",
"motivo_falha": null
}
}
EventWhendata.status
email.sentThe mail service accepted the messageEnviado
email.failedFinal failure, data.motivo_falha explainsFalha

email.sent means the mail service accepted the message, not that it reached the recipient inbox. The full field description is in Webhooks.

Error model

The endpoints return a bare array:

json
[{"codigo": "10021013", "mensagem": "Sem destinatário: destinatarios não informado e o documento não tem e-mail do cliente"}]
CodeHTTPMeaningWhat to do
10001001400Field validation failedFix as the message says
10003000404Company not foundCheck empresaId
10021001404Template not found or owned by another integratorCheck templateId
10021002400Template disabled or of a different type than the documentSwitch template or re-enable it
10021003400Template uses variables outside the allowed listFix the names listed in the message
10021004400Template HTML has forbidden content or exceeds 64 KBFix the positions listed in the message
10021005400Unsupported document typeUse only NFe / NFSe / CTe / DCe
10021010404Document not found or owned by another companyCheck documentoId and tipoDocumento
10021011400Document not authorized yetSend after authorization
10021012400Type without a default templatePass templateId or mark a default
10021013400No recipientPass destinatarios
10021015409Previous e-mail of the same document in flightWait for the terminal state and resend with a new id
10021020404E-mail not found or owned by another companyCheck emailId

Authentication and authorization errors (10009xxx) are in Authentication; the full table is in Error codes.

Integration checklist

  1. Save an NFe template with padrao = true and check that variaveisDisponiveis in the response has 11 items.
  2. Deliberately write {{codigoVerificacao}} in the body and check 10021003; switch the image to an http address and check 10021004.
  3. List email.sent and email.failed in the webhook eventos.
  4. Send an authorized NF-e (enviarPdf and enviarXml set to true, no destinatarios); check the Enfileirado acceptance, then email.sent, and assunto and enviadoEm filled in the query.
  5. Before the terminal state, send the same document with another id and check 409 with 10021015; repeat with the same id and check the same emailId.
  6. Send a São Paulo NFS-e and check the Aguardando acceptance with motivoEspera = chaveAcesso, moving to Enviado on its own once the key arrives.