developertestingAPIemail testing

Como construir um ambiente de teste de email com uma API de email descartável

Construa um ambiente robusto de teste de email usando caixas de entrada API de email descartável com IMAP e SMTP real — inclui exemplos Python e Node.js.

September 9, 2025·7 min de leitura·Reusable.Email
Como construir um ambiente de teste de email com uma API de email descartável

Teste de email é um daqueles problemas que parecem simples e não são. Sua aplicação envia emails — confirmações de cadastro, redefini senhas, notificações, faturas. Você precisa verificar que esses emails realmente chegam, contêm o conteúdo correto e renderizam corretamente. Fazer isso confiavels em um suíte de testes automatizados é mais difícil do que deveria ser.

Por que teste de email é difícil

A dificuldade principal: você não pode enviar emails de teste para usuários reais. Não em CI, não em staging, nunca. Então você precisa de uma alternativa.

Caixas de areia de email (Mailtrap, Mailhog) interceptam email de saída e o mostram em um painel. Elas são úteis para inspeção visual, mas não testam entrega real. Seu email nunca realmente chega a uma caixa de entrada. Você não pode verificar busca IMAP, renderização de cliente ou fluxos fim a fim.

Contas de teste compartilhadas (um endereço Gmail que o time usa) criam testes instáveis. Limites de taxa, estado compartilhado, rotação de credenciais — tudo isso quebra pipelines de CI no pior momento possível.

A abordagem ideal: caixas de entrada IMAP/SMTP reais que você cria sob demanda e usa em testes. Entrega real, protocolos reais, isolado por suite de testes. Esse é o que uma API de email descartável oferece.

A abordagem: caixas de entrada reais para cada teste

Com caixas de entrada gerenciadas do Reusable.Email, cada ambiente de teste obtém sua própria conta de email real. A caixa de entrada tem credenciais IMAP e SMTP padrão, então seus testes usam as mesmas bibliotecas e protocolos que seu código de produção.

O fluxo:

  1. Criar uma caixa de entrada gerenciada (ou use uma pré-criada para sua suite de testes)
  2. Configure sua aplicação sob teste para enviar para aquela caixa de entrada
  3. Após a ação disparar, se conecte via IMAP e faça asserções no conteúdo de email
  4. A caixa de entrada persiste por 365 dias — reutilize-a entre execuções de teste

A $3 por caixa de entrada (uma única vez), o custo é trivial. Dez caixas de entrada de teste permanentes para todo seu pipeline de CI custam $30 total.

Exemplo Python: enviar e verificar um email

Este exemplo simula um fluxo de teste comum: sua aplicação envia um email de boas-vindas e seu teste verifica que chegou com o conteúdo correto.

import imaplib
import smtplib
import email
import time
from email.mime.text import MIMEText
from email.header import decode_header

# Credenciais de caixa de entrada gerenciada
INBOX_USER = "test-suite@reusable.email"
INBOX_PASS = "sua-senha-de-caixa-de-entrada"
IMAP_HOST = "imap.reusable.email"
SMTP_HOST = "smtp.reusable.email"

def send_test_email(to_address, subject, body):
    """Envie um email via SMTP (simula sua app enviando uma notificação)."""
    msg = MIMEText(body)
    msg["Subject"] = subject
    msg["From"] = INBOX_USER
    msg["To"] = to_address

    with smtplib.SMTP(SMTP_HOST, 587) as server:
        server.starttls()
        server.login(INBOX_USER, INBOX_PASS)
        server.send_message(msg)

def wait_for_email(subject_contains, timeout=30):
    """Poll IMAP até um email com assunto correspondente chegar."""
    imap = imaplib.IMAP4_SSL(IMAP_HOST, 993)
    imap.login(INBOX_USER, INBOX_PASS)

    start = time.time()
    while time.time() - start < timeout:
        imap.select("INBOX")
        status, messages = imap.search(None, "UNSEEN")
        for msg_id in messages[0].split():
            status, msg_data = imap.fetch(msg_id, "(RFC822)")
            msg = email.message_from_bytes(msg_data[0][1])
            subject = decode_header(msg["Subject"])[0][0]
            if isinstance(subject, bytes):
                subject = subject.decode()
            if subject_contains.lower() in subject.lower():
                imap.logout()
                return msg
        time.sleep(2)

    imap.logout()
    raise TimeoutError(f"Nenhum email correspondendo a '{subject_contains}' dentro de {timeout}s")

# --- Fluxo de teste ---
send_test_email(INBOX_USER, "Bem-vindo ao nosso app", "Obrigado por se inscrever!")
received = wait_for_email("Bem-vindo ao nosso app")
assert "Obrigado por se inscrever!" in received.get_payload(decode=True).decode()
print("Teste passou: email de boas-vindas recebido e verificado.")

Este é um teste real fim a fim. O email é realmente enviado via SMTP, realmente entregue e realmente lido via IMAP. Sem mocks, sem caixas de areia.

Exemplo Node.js: enviar e ler de volta

O mesmo padrão em Node.js, usando nodemailer para envio e imapflow para recebimento:

const nodemailer = require("nodemailer");
const { ImapFlow } = require("imapflow");

const INBOX_USER = "test-suite@reusable.email";
const INBOX_PASS = "sua-senha-de-caixa-de-entrada";

// Envie um email via SMTP
async function sendEmail(subject, body) {
  const transporter = nodemailer.createTransport({
    host: "smtp.reusable.email",
    port: 587,
    secure: false,
    auth: { user: INBOX_USER, pass: INBOX_PASS },
  });

  await transporter.sendMail({
    from: INBOX_USER,
    to: INBOX_USER,
    subject,
    text: body,
  });
}

// Poll IMAP para um email correspondente
async function waitForEmail(subjectContains, timeoutMs = 30000) {
  const client = new ImapFlow({
    host: "imap.reusable.email",
    port: 993,
    secure: true,
    auth: { user: INBOX_USER, pass: INBOX_PASS },
  });

  await client.connect();
  const start = Date.now();

  while (Date.now() - start < timeoutMs) {
    const lock = await client.getMailboxLock("INBOX");
    try {
      for await (const message of client.fetch({ seen: false }, { source: true })) {
        const parsed = require("mailparser").simpleParser;
        const mail = await parsed(message.source);
        if (mail.subject && mail.subject.includes(subjectContains)) {
          await client.logout();
          return mail;
        }
      }
    } finally {
      lock.release();
    }
    await new Promise((r) => setTimeout(r, 2000));
  }

  await client.logout();
  throw new Error(`Nenhum email correspondendo a '${subjectContains}' dentro do timeout`);
}

// --- Fluxo de teste ---
(async () => {
  await sendEmail("Redefinir senha", "Seu código de reset é 123456");
  const mail = await waitForEmail("Redefinir senha");
  console.assert(mail.text.includes("123456"), "Código de reset deve estar no corpo do email");
  console.log("Teste passou: email de redefinição de senha verificado.");
})();

Integração CI/CD

Para integração contínua, as principais decisões são:

Pré-crie caixas de entrada, não crie por execução. Uma vez que caixas de entrada gerenciadas são permanentes (retenção de 365 dias) e custam $3 uma única vez, crie um conjunto de caixas de entrada de teste antecipadamente e armazene as credenciais como segredos de CI. Isto evita a necessidade de chamadas de API para criar caixas de entrada durante o pipeline.

Uma caixa de entrada por suite de testes, não por teste. A menos que seus testes enviem emails conflitantes para o mesmo endereço, uma única caixa de entrada por suite funciona. Use assuntos ou IDs de mensagem únicos para distinguir entre emails de teste.

Limpe entre execuções. Antes de cada execução de teste, ou marque todas as mensagens existentes como lidas ou delete-as via IMAP. Isto evita que emails antigos causem falsos positivos.

# Limpe caixa de entrada antes da execução de teste
def clean_inbox():
    imap = imaplib.IMAP4_SSL("imap.reusable.email", 993)
    imap.login(INBOX_USER, INBOX_PASS)
    imap.select("INBOX")
    status, messages = imap.search(None, "ALL")
    for msg_id in messages[0].split():
        imap.store(msg_id, "+FLAGS", "\\Seen")
    imap.logout()

Armazene credenciais com segurança. Trate credenciais de caixa de entrada como qualquer outro segredo em CI — use variáveis de ambiente, não valores codificados.

Trate timeouts graciosamente. Entrega de email pode levar alguns segundos. Defina seu timeout de polling alto o suficiente para evitar testes instáveis (30 segundos é um padrão razoável), mas não tão alto que um email genuinamente faltante pendure seu pipeline por minutos.

Integração de Framework

A maioria dos frameworks de teste suportam hooks de setup/teardown que tornam o gerenciamento de caixa de entrada limpo:

# exemplo pytest
import pytest

@pytest.fixture(autouse=True)
def clean_test_inbox():
    """Marque todos os emails como lidos antes de cada teste."""
    imap = imaplib.IMAP4_SSL("imap.reusable.email", 993)
    imap.login(INBOX_USER, INBOX_PASS)
    imap.select("INBOX")
    status, messages = imap.search(None, "ALL")
    for msg_id in messages[0].split():
        imap.store(msg_id, "+FLAGS", "\\Seen")
    imap.logout()
    yield
    # Teardown: nada necessário, emails persistem para referência da próxima execução

def test_welcome_email(app_client):
    """Verifique que cadastro envia um email de boas-vindas."""
    app_client.post("/signup", json={"email": INBOX_USER, "name": "Test"})

    imap = imaplib.IMAP4_SSL("imap.reusable.email", 993)
    imap.login(INBOX_USER, INBOX_PASS)
    msg = wait_for_email(imap, "Bem-vindo")
    body = msg.get_payload(decode=True).decode()

    assert "Bem-vindo" in msg["Subject"]
    assert "Test" in body
    imap.logout()

Análise de custo

Cenário Caixas de entrada Custo
Projeto pequeno, única caixa de entrada de teste 1 $3 (uma única vez)
CI de time com suite por serviço 5 $15 (uma única vez)
Matriz de teste completa (dev, staging, CI) 10 $30 (uma única vez)
Org grande, caixas de entrada por time 50 $150 (uma única vez)

Compare com um serviço de caixa de areia em $15-35/mês ($180-420/ano) e a economia é clara. As caixas de entrada de teste que você cria hoje ainda funcionarão um ano depois, sem renovação.

Para times que precisam de criação programática de caixa de entrada (centenas de caixas de entrada, isolamento por teste), a camada whitelabel em $30/mês inclui criação de caixa de entrada ilimitada via API.

Depurando testes de email falhados

Quando um teste de email falha, a causa é geralmente uma destas:

Email ainda não chegou. Aumente seu timeout de polling. Entrega SMTP não é instantânea — mensagens podem levar alguns segundos para atravessar o pipeline. Um timeout de 30 segundos é razoável para a maioria das configurações.

Caixa de entrada errada. Verifique novamente que sua aplicação está enviando para o mesmo endereço que seu teste está fazendo poll. Um erro comum é configurar a app para enviar para um endereço diferente do que seu código IMAP se conecta.

Email já foi lido. Se uma execução de teste anterior (ou um teste anterior na mesma execução) já marcou o email como visto, sua busca UNSEEN não o encontrará. Use o fixture de limpeza acima ou procure por mensagens por assunto e data em vez de confiar apenas na flag não vista.

Autenticação SMTP falhou. Verifique que suas credenciais SMTP correspondem às credenciais de caixa de entrada gerenciada. Verifique que você está usando porta 587 com STARTTLS, não porta 465 com SSL implícito.

Restrições de firewall ou rede. Alguns ambientes de CI restringem conexões de saída. Garanta que portas 587 (SMTP) e 993 (IMAP) são permitidas na configuração de rede do seu provedor de CI.

Próximas etapas

Esta abordagem oferece teste de email real com protocolos padrão. Para mais sobre casos de uso mais amplos de desenvolvedor — isolamento de staging, caixas de entrada por usuário, construir produtos de email — veja o guia API de email para desenvolvedores.

Para detalhes sobre configuração SMTP especificamente, incluindo quando servidores SMTP falsos fazem mais sentido que entrega real, leia Teste SMTP: teste emails de saída sem enviar para caixas de entrada reais.

E se você está avaliando o cenário de serviços de email descartável mais amplamente, aquele guia cobre o espectro completo de caixas de entrada públicas livres para contas gerenciadas.

Try it free

Get a disposable inbox in seconds

No sign-up required. Just visit an address and it's live. Works with any domain on reusable.email.

Open your inbox →