Referência da API
POST /api/v1/send é toda a Send API transacional — um único endpoint versionado e estável. Diferente do restante das rotas do MailInApp (que só têm nosso próprio frontend como chamador e podem mudar livremente), esta é um contrato do qual código de terceiros depende, por isso é versionada desde o primeiro dia.
Autenticação
Authorization: Bearer mia_live_...
Uma chave ausente ou inválida retorna 401. As chaves são gerenciadas em Developers, no painel — veja o guia rápido. Uma chave revogada para de funcionar imediatamente.
Requisição
Content-Type: application/json. Dois formatos de requisição compartilham o endpoint — qual deles se aplica é decidido pela presença ou não de projectId.
Livre
| Campo | Tipo | Obrigatório | Notas |
| --- | --- | --- | --- |
| to | string | sim | Um único endereço de destinatário. |
| subject | string | sim | Truncado para 200 caracteres. |
| html | string | sim | Enviado como está — sem renderização, sem tags de personalização, sem passar pelo pipeline do estúdio. |
| text | string | sim | Parte em texto simples. |
Modelo
| Campo | Tipo | Obrigatório | Notas |
| --- | --- | --- | --- |
| to | string | sim | Um único endereço de destinatário. |
| projectId | string | sim | Precisa ser um projeto seu — a URL do estúdio é /studio/<projectId>. |
| mergeData | object | não | Objeto simples { chave: valor }, com até 100 campos. Faz o papel de uma linha da fonte de dados — resolve para as tags de personalização {{campo}} do projeto. Objetos/arrays aninhados são descartados; null/undefined se tornam strings vazias; qualquer outra coisa é convertida para string. |
| subject | string | não | Usa o nome do projeto por padrão, se omitido. Tags de personalização no assunto resolvem a partir de mergeData. Truncado para 200 caracteres. |
Um corpo que não corresponde a nenhum dos dois formatos (por exemplo, faltando to, ou faltando tanto html/text quanto projectId) retorna 400.
Campos comuns
| Campo | Tipo | Obrigatório | Notas |
| --- | --- | --- | --- |
| type | "transactional" | "marketing" | não | Usa "transactional" por padrão. Veja abaixo. |
| from | object | não | Substituição do endereço/nome do remetente por chamada — { "email": string, "name"?: string }. Veja abaixo. |
| senderId | string | não | Substituição por chamada, escolhendo uma das identidades de remetente salvas da sua conta, em vez de escrever from embutido. Precisa pertencer à sua conta, senão 400. from prevalece se ambos forem informados. |
| replyTo | string | não | Endereço de Reply-To por chamada. Veja abaixo. |
Cabeçalhos
| Cabeçalho | Obrigatório | Notas |
| --- | --- | --- |
| Authorization | sim | Bearer <apiKey>. |
| Idempotency-Key | não | Veja idempotência. |
Resposta
{ "id": "abc123", "status": "sent" }
status é um dos seguintes:
| Status | Significado |
| --- | --- |
| sent | Entregue com sucesso ao seu método de envio (relay SMTP ou envio nativo). |
| suppressed | O destinatário está na sua lista de supressão — veja transacional vs. marketing. Nenhum e-mail foi enviado; isso não é um erro. |
| failed | A tentativa de envio falhou (por exemplo, seu relay SMTP não está configurado, ou rejeitou a mensagem). Um campo error traz um motivo legível por humanos. |
Códigos de erro
| Status | Significado |
| --- | --- |
| 400 | JSON malformado, uma requisição que não corresponde nem ao formato livre nem ao de modelo, um type inválido, um from inválido ou não permitido (veja endereço de remetente), um replyTo inválido, ou (no modo modelo) um projectId que não existe ou não é seu. Também retornado quando a conta não tem nenhum método de envio funcional configurado. |
| 401 | Cabeçalho Authorization ausente ou inválido, ou a chave foi revogada. |
| 404 | (No modo modelo) o projeto não existe ou não pertence à sua conta — deliberadamente a mesma resposta de "não existe", para evitar revelar quais IDs de projeto são válidos para outras contas. |
| 429 | Limite de taxa excedido — veja limites de taxa. |
Um 200 com status: "failed" (em vez de um status não 2xx) é retornado quando a própria requisição era válida, mas a tentativa de envio em si falhou mais adiante. Se você precisa distinguir "rejeitamos sua requisição" de "tentamos, mas não saiu", verifique o status no corpo, não apenas o código de status HTTP.
Transacional vs. marketing
O campo type controla qual lista de supressão é verificada, espelhando como os ESPs separam os fluxos transacional e de marketing:
type: "transactional"(padrão) — ignora a supressão por cancelamento de inscrição. Uma redefinição de senha ou um recibo de pedido não deveriam ser bloqueados só porque o destinatário cancelou a inscrição na sua newsletter. Ela nunca ignora a supressão por bounce — um endereço morto está morto, independentemente da intenção.type: "marketing"— se comporta exatamente como um envio de campanha feito pelo painel: bloqueado tanto pela supressão por cancelamento de inscrição quanto pela supressão por bounce.
Nos dois casos, quando bloqueado, o retorno é status: "suppressed", não um erro.
Endereço de remetente
Por padrão, todo envio usa a identidade de remetente configurada na sua conta — o cartão "From address" de Configurações → Domínios, se você estiver no envio nativo (SES), ou o endereço de remetente configurado no seu relay SMTP, caso contrário. Passe from para substituí-lo em uma única chamada:
{
"to": "[email protected]",
"subject": "Your one-time code",
"html": "<p>Your code is 123456</p>",
"text": "Your code is 123456",
"from": { "email": "[email protected]", "name": "FitConsent Sales Team" }
}
from.email é obrigatório sempre que from estiver presente — não existe substituição só do nome, então um valor aqui sempre substitui completamente tanto o endereço quanto o nome de exibição para aquela chamada. from.name é opcional; omita-o para enviar apenas com o endereço puro.
Se sua conta envia por um domínio verificado (envio nativo/SES), from.email precisa ser um endereço em um dos seus próprios domínios de envio verificados (por exemplo, [email protected], não [email protected]) — uma conta pode verificar mais de um domínio, então qualquer um deles é permitido, só nunca o de outra pessoa. O envio nativo roda em uma única conta AWS da plataforma, compartilhada entre todos os clientes do MailInApp, então essa restrição é o que impede uma conta de enviar e-mail que pareça vir do domínio verificado de outra. No envio por SMTP não existe essa restrição: o envio sai pelo seu próprio relay/credenciais, então já é confiável do mesmo jeito que as próprias regras de verificação de remetente do seu relay já são.
senderId (veja a tabela de campos comuns acima) geralmente é a opção mais simples quando você já configurou uma identidade de remetente no painel. Ele resolve para o próprio {email, name, reply-to} daquela identidade, sem que você precise repeti-los em cada chamada.
Um from inválido ou não permitido retorna 400 antes de qualquer tentativa de envio.
Endereço de Reply-To
Por padrão, as respostas vão para o Reply-To padrão configurado na sua conta, definido na página Sending ou Domains em Configurações, dependendo do seu método de envio, ou para lugar nenhum, se você não definiu um. Passe replyTo para substituí-lo em uma única chamada — útil quando o from visível é um endereço no-reply, mas você ainda quer que uma pessoa veja as respostas:
{
"to": "[email protected]",
"subject": "Your order shipped",
"html": "<p>Your order is on its way.</p>",
"text": "Your order is on its way.",
"from": { "email": "[email protected]", "name": "FitConsent" },
"replyTo": "[email protected]"
}
Diferente de from.email, replyTo não tem nenhuma restrição de domínio no envio nativo (SES) — ele nunca afeta a identidade de envio ou a reputação de entregabilidade, é só um cabeçalho que o cliente de e-mail do destinatário respeita quando ele clica em responder. Um replyTo inválido retorna 400 antes de qualquer tentativa de envio.
Idempotência
Passe um cabeçalho Idempotency-Key em qualquer chamada que possa ser repetida — uma reentrega de webhook de checkout, um consumidor de fila reprocessando uma mensagem. Uma chamada repetida com a mesma chave, para a mesma conta, retorna o {id, status} da chamada original, sem enviar um segundo e-mail, mesmo que a primeira chamada ainda esteja em andamento.
As chaves têm escopo por conta e não têm expiração; evitar reutilizar uma chave que você já usou para um payload diferente é sua própria responsabilidade (ela ainda vai retornar o resultado da primeira chamada, não enviar o novo payload). Se você não passar uma chave, toda chamada envia.
Limites de taxa
Três limites independentes se aplicam:
- Por chave de API: um limite de requisições por minuto. Excedê-lo retorna
429especificamente para aquela chave — outras chaves na mesma conta não são afetadas. - Por conta, cota da Send API: seu plano inclui um número de chamadas da Send API por mês civil (500/mês no Free). Excedê-lo retorna
429até a cota ser reiniciada no dia 1º. - Por conta, volume de envio: o limite mensal cumulativo de volume de envio do seu plano, compartilhado entre todos os caminhos de envio (envios pelo painel, envios agendados, e-mails de ciclo de vida e esta API). É o mesmo limite que protege o restante do envio da sua conta — a Send API não tem um orçamento separado.
Especificidades do modo modelo
Um envio no modo modelo renderiza o projeto vinculado exatamente como um envio normal a um destinatário: as três camadas do motor de fallback, os blocos interativos e um link de visualização ao vivo pessoal e assinado, construído a partir de mergeData em vez de uma linha de contato armazenada. Tudo o que vem depois se comporta da mesma forma que um envio feito pelo estúdio:
- Votos em enquetes, avaliações, envios de formulário e aberturas são registrados e aparecem na página de Respostas do projeto, atribuídos a esta chamada de API específica, e não a uma linha de contato.
- O webhook do seu projeto é disparado a cada evento de interação, igual a qualquer outro destinatário.
- Chamadas recentes (status, destinatário, carimbo de data/hora) são listadas na página Developers do painel, como um registro de auditoria.
A única coisa diferente de um envio pelo painel: não existe uma linha de fonte de dados armazenada por trás da interação, então os joins do seu lado devem se basear no identificador de destinatário da resposta, não em um índice de linha.