Pular para o conteúdo
Docs

Guia prático

Processar e-mails recebidos com webhooks

Inscreva um webhook em email.received, verifique cada requisição e obtenha com a API o corpo, os cabeçalhos e os anexos de cada mensagem recebida.

Atualizado em 1 de out. de 2026

Este guia mostra como reagir a e-mails recebidos no seu próprio código. O Emailit notifica o seu endpoint com um evento email.received, e o seu código obtém a mensagem completa com a API. Os exemplos verificam a assinatura, tratam lotes de eventos, baixam os anexos e ignoram duplicatas.

Antes de começar

  • O recebimento está configurado e uma mensagem de teste aparece na aba Incoming.
  • Uma chave de API com Full Access. Não é possível ler o conteúdo dos e-mails com chaves Sending Only. Consulte Chaves de API.
  • Um endpoint HTTPS público que aceite requisições POST. Para desenvolvimento local, use um túnel como ngrok ou Cloudflare Tunnel.

Como o fluxo funciona

O webhook avisa que uma mensagem chegou. Ele não contém o corpo nem os anexos, o que mantém as requisições pequenas e permite que você obtenha o conteúdo apenas quando precisar.

email.received (um evento no array da requisição)
{
  "event_id": "evt_2xGk9Tb4QmF6wN2cJpR7uZsE1kD",
  "type": "email.received",
  "object": {
    "id": "em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA",
    "object": "email",
    "from": "ada@example.com",
    "to": "support@inbound.acme.com",
    "subject": "Question about order 1042",
    "created_at": "2026-10-01T09:14:05.317000+00:00"
  },
  "data": {
    "object": {
      "id": "em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA",
      "object": "email",
      "from": "ada@example.com",
      "to": "support@inbound.acme.com",
      "subject": "Question about order 1042",
      "created_at": "2026-10-01T09:14:05.317000+00:00"
    }
  }
}

Use data.object.id com Obter um e-mail para receber a mensagem já interpretada:

GET /v2/emails/em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA (resumido)
{
  "object": "email",
  "id": "em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA",
  "type": "inbound",
  "status": "received",
  "from": "ada@example.com",
  "to": "support@inbound.acme.com",
  "subject": "Question about order 1042",
  "message_id": "<CAF3x1@mail.example.com>",
  "headers": {
    "From": "Ada Lovelace <ada@example.com>",
    "Reply-To": "ada@example.com",
    "Date": "Thu, 01 Oct 2026 09:14:02 +0000"
  },
  "body": {
    "text": "Hi, my order hasn't arrived yet...",
    "html": "<p>Hi, my order hasn't arrived yet...</p>"
  },
  "attachments": [
    {
      "filename": "receipt.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "content_id": null,
      "content_disposition": "attachment",
      "content": "JVBERi0xLjcKJcfsj6IK..."
    }
  ]
}

O content dos anexos vem codificado em Base64. Os nomes dos cabeçalhos mantêm a capitalização original, e quando um cabeçalho aparece mais de uma vez (como Received), apenas o último valor é mantido. Se você só precisar de parte da mensagem, use os endpoints mais específicos: Obter o corpo, Listar anexos ou Obter o MIME bruto para o código-fonte original.

Criar o webhook

  1. Adicione o webhook. Acesse Email APIWebhooks, selecione Add webhook, digite um nome e a URL do seu endpoint e selecione Create.

  2. Copie o segredo. A caixa de diálogo mostra o segredo do webhook (whsec_…) uma única vez. Guarde-o como EMAILIT_WEBHOOK_SECRET no ambiente da sua aplicação.

  3. Inscreva-o apenas em email.received. Na aba Settings do webhook, desative All events, selecione email.received em Emails e selecione Save.

Nos planos Pro, Business e Custom, você pode adicionar um filtro de payload para que o webhook receba apenas os e-mails de alguns endereços, por exemplo to terminando em @inbound.acme.com.

Escrever o handler

O corpo de cada requisição é um array JSON de até 100 eventos. O handler abaixo verifica a assinatura, percorre o array, ignora tudo o que não for email.received ou que já tenha sido processado e obtém cada mensagem.

server.js
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const WEBHOOK_SECRET = process.env.EMAILIT_WEBHOOK_SECRET; // whsec_...
const API_KEY = process.env.EMAILIT_API_KEY; // secret_..., Full Access
const processed = new Set(); // use a database table with a unique key in production

function verify(rawBody, signature, timestamp) {
  if (!signature || !timestamp) return false;
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!(age <= 300)) return false; // 5-minute tolerance
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

async function fetchEmail(id) {
  const res = await fetch(`https://api.emailit.com/v2/emails/${id}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`Emailit API returned ${res.status}`);
  return res.json();
}

async function handleReceived(event) {
  const email = await fetchEmail(event.data.object.id);
  console.log(`From ${email.from} to ${email.to}: ${email.subject}`);
  console.log(email.body.text ?? email.body.html);

  for (const file of email.attachments ?? []) {
    const bytes = Buffer.from(file.content, 'base64');
    console.log(`Attachment ${file.filename} (${file.content_type}, ${bytes.length} bytes)`);
  }
}

// Keep the raw body: the signature is computed over the exact bytes.
app.post('/webhooks/emailit', express.raw({ type: 'application/json' }), async (req, res) => {
  if (!verify(req.body, req.get('X-Emailit-Signature'), req.get('X-Emailit-Timestamp'))) {
    return res.status(401).send('Invalid signature');
  }

  const events = JSON.parse(req.body.toString('utf8'));
  try {
    for (const event of events) {
      if (event.type !== 'email.received' || processed.has(event.event_id)) continue;
      await handleReceived(event);
      processed.add(event.event_id);
    }
    res.sendStatus(200);
  } catch (err) {
    console.error(err);
    res.sendStatus(500); // Emailit retries the whole batch later
  }
});

app.listen(3000);

Tratar novas tentativas e duplicatas

  • Responda em até 30 segundos. O Emailit espera até 30 segundos por uma resposta 2xx. Um timeout, um status diferente de 2xx ou um redirecionamento contam como falha, e o lote inteiro recebe novas tentativas conforme o cronograma de novas tentativas. Se obter e processar as mensagens puder demorar mais, guarde os eventos em uma fila, retorne 200 e processe-os em um job em segundo plano.
  • Elimine duplicatas pelo event_id. Um lote reenviado contém de novo todos os eventos dele, incluindo os que você já tratou antes da falha. Registre cada event_id depois de processá-lo e ignore os IDs que você já viu. Cada mensagem recebida também tem o seu próprio ID de e-mail, que você pode usar como segunda chave.
  • Confirme o recebimento dos eventos que você não trata. Retorne 2xx mesmo quando um lote contiver apenas tipos de evento que você ignora. Caso contrário, o Emailit continua tentando enviá-los de novo.
  • Obtenha o conteúdo logo. O conteúdo das mensagens é guardado por um tempo limitado que depende do seu plano (7 dias no Pay as you go). Depois disso, a API retorna o e-mail sem o corpo nem os anexos. Consulte Retenção de dados.

Rotear mensagens pelo endereço

Como qualquer parte local é aceita, você pode codificar informações no endereço e lê-las de volta em to. Por exemplo, envie notificações com reply_to definido como reply+4821@inbound.acme.com e depois encaminhe as respostas para o ticket 4821:

JavaScript
const match = email.to.match(/^reply\+(\d+)@inbound\.acme\.com$/i);
if (match) {
  await addReplyToTicket(match[1], email.body.text);
}

Confirmar que funcionou

  1. Envie uma mensagem para um endereço do seu subdomínio de recebimento.
  2. Na aba Requests do webhook, a requisição email.received aparece como Delivered.
  3. A sua aplicação registra no log o remetente, o assunto e os anexos, se houver.

Se a requisição aparecer como Attempting ou Failed, selecione View em uma linha com falha para ver o código de status e o corpo da resposta que o seu endpoint retornou. Consulte Novas tentativas e falhas.

Esta página foi útil?

Obrigado pelo feedback.

Obrigado, lemos todas as mensagens.