Agentes de IA & MCP

E-mail para agentes de IA: verificar cadastros sem humano

Um agente capaz de navegar, preencher formulários e pagar ainda trava completamente em “confira seu e-mail para ver o código”, porque ele não tem caixa de entrada. Aqui está como dar a ele uma que não precisa de conta nem de chave — via MCP com uma espera bloqueante, ou como uma função de ferramenta simples para LangChain, o OpenAI Agents SDK e o Vercel AI SDK — com o loop de quatro passos que leva um cadastro até o fim e as proteções que um agente sem supervisão precisa.

  • Intermediário
  • 21 min de leitura
Um pequeno robô cinza em uma mesa cinza erguendo um envelope azul, uma mão sobre um teclado, com um sinal de visto azul flutuando acima

A parede em que todo agente esbarra

Cadastrar-se para um teste gratuito, criar um workspace, resgatar uma chave de API, entrar em um beta: cada uma dessas coisas termina em um formulário, e todo formulário termina em uma caixa de entrada. Um humano dá uma olhada no celular. Um agente não tem nada para olhar — ele não tem um endereço que consiga ler, e os mais sensatos param e pedem o código a você, o que anula todo o sentido de ter enviado um agente.

A solução não é um prompt mais esperto. É uma caixa de entrada que o agente consegue ler de forma programática, sem conta para criar antes (um agente que cria uma conta de e-mail esbarra na mesma parede um nível abaixo) e sem chave para gerenciar. Uma caixa de entrada descartável é exatamente isso: uma caixa de entrada existe no momento em que o e-mail chega a ela, e lê-la é uma única requisição HTTP.

Duas formas de dar a ele uma caixa de entrada

A mesma caixa de entrada é acessível de duas formas, e a escolha depende de como o agente é construído, não da caixa de entrada:

CritérioServidor MCPFunção de ferramenta REST
Serve quandoO agente roda em um cliente MCP — Claude Code, Cursor, Claude Desktop, ou um framework com um adaptador MCP.Você escreve o agente em código: LangChain, o OpenAI Agents SDK, o Vercel AI SDK, ou o seu próprio loop.
Esperawait_for_message bloqueia do lado do servidor por até 25 segundos e retorna a mensagem inteira. Zero tokens gastos durante a espera.A função de ferramenta faz um loop de uma vez por segundo até o prazo. Zero tokens também — o loop está no seu código, não no modelo.
ConfiguraçãoUma URL na configuração do cliente. Nenhum código.Duas funções, quarenta linhas, uma biblioteca HTTP.
O que o modelo vêSeis ferramentas com descrições, mais um parágrafo de instruções que o servidor envia no momento da conexão.O que quer que as descrições das suas ferramentas digam. As docstrings abaixo são escritas justamente para isso.

O caminho via MCP é uma linha, e a configuração por cliente tem a linha exata para sete clientes; uma caixa de entrada que um agente de IA consegue ler explica as ferramentas em profundidade. O restante deste guia é o caminho via REST, para agentes que você mesmo constrói.

o caminho via MCP, por completo
{"mcpServers":{"grabmail":{"url":"https://grabmail.io/mcp"}}}

Uma função de ferramenta: Python, para LangChain e o OpenAI Agents SDK

Duas funções simples com docstrings cuidadosas. As docstrings importam mais do que o código: nos dois frameworks elas se tornam a descrição que o modelo lê para decidir quando chamar a ferramenta e o que fazer com a resposta, então elas dizem as duas coisas que um agente erra — alias versus endereço, e o que timed_out significa.

inbox_tools.py
"""inbox_tools.py — two plain functions any agent framework can wrap. No key, no account."""
import secrets
import time

import requests

API = "https://grabmail.io/api/v1"


def create_inbox() -> dict:
    """Create a fresh disposable email inbox for this task.

    Returns the ADDRESS to poll and the ALIAS to give to websites. Put the alias
    into forms; never hand out the address. Nothing is created server-side.
    """
    address = f"agent-{secrets.token_hex(4)}@grabmail.io"
    r = requests.get(f"{API}/mailbox", params={"address": address}, timeout=15)
    r.raise_for_status()
    return {
        "address": address,
        "alias": r.json().get("alias"),
        "next_step": "Put the alias into the form. Then call wait_for_message with the address.",
    }


def wait_for_message(address: str, subject_contains: str = "", timeout_seconds: int = 60) -> dict:
    """Wait for an email to arrive at the address, up to timeout_seconds.

    Returns the message (from, subject, text, html) or {"status": "timed_out"}.
    On timed_out, call again — up to three times — before concluding no mail was sent.
    """
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        r = requests.get(f"{API}/mailbox", params={"address": address}, timeout=15)
        if r.status_code == 429:                       # slow down, do not fail
            time.sleep(float(r.headers.get("Retry-After", 1)))
            continue
        r.raise_for_status()
        for m in r.json()["messages"]:
            if subject_contains.lower() in m["subject"].lower():
                full = requests.get(f"{API}/message/{m['id']}", params={"mailbox": address}, timeout=15)
                full.raise_for_status()
                return full.json()
        time.sleep(1)                                   # one read a second, never throttled
    return {"status": "timed_out", "hint": "Call again, up to three times, before giving up."}

Envolvê-las é uma chamada por framework. O tool do LangChain lê a docstring e os type hints; o function_tool do Agents SDK faz o mesmo e adiciona as ferramentas a um agente cujas instruções repetem o loop:

LangChain
# LangChain: the docstring becomes the tool description the model reads.
from langchain_core.tools import tool

create_inbox_tool = tool(create_inbox)
wait_for_message_tool = tool(wait_for_message)
# agent = create_react_agent(model, tools=[create_inbox_tool, wait_for_message_tool, ...])
OpenAI Agents SDK
# OpenAI Agents SDK: same two functions, same docstrings.
from agents import Agent, Runner, function_tool

signup_agent = Agent(
    name="Signup agent",
    instructions=(
        "When a site needs an email address, call create_inbox once. Put the ALIAS in the form. "
        "Right after submitting, call wait_for_message with the ADDRESS and a word from the expected "
        "subject. If it returns timed_out, call it again, up to three times."
    ),
    tools=[function_tool(create_inbox), function_tool(wait_for_message)],
)

result = Runner.run_sync(signup_agent, "Sign up for a trial at https://app.example.com/signup and report the login.")
print(result.final_output)

A mesma ferramenta em TypeScript, para o Vercel AI SDK

O tool() do AI SDK recebe uma description, um schema e um execute; a description carrega as mesmas duas frases. Passe as duas ferramentas para generateText ou streamText com um maxSteps acima de quatro, porque o loop tem quatro chamadas de ferramenta de comprimento:

inbox-tools.ts
// inbox-tools.ts — the same two tools for the Vercel AI SDK (v5 shape: inputSchema + execute).
import { tool } from 'ai';
import { z } from 'zod';

const API = 'https://grabmail.io/api/v1';
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

export const createInbox = tool({
  description: 'Create a fresh disposable email inbox for this task. Returns the ADDRESS to poll and the ALIAS to give to websites. Put the alias into forms; never hand out the address.',
  inputSchema: z.object({}),
  execute: async () => {
    const address = `agent-${crypto.randomUUID().slice(0, 8)}@grabmail.io`;
    const res = await fetch(`${API}/mailbox?address=${encodeURIComponent(address)}`);
    const { alias } = (await res.json()) as { alias: string | null };
    return { address, alias, next_step: 'Put the alias into the form. Then call waitForMessage with the address.' };
  },
});

export const waitForMessage = tool({
  description: 'Wait for an email to arrive at the address, up to timeoutSeconds. Returns the message (from, subject, text, html) or { status: "timed_out" }. On timed_out, call again — up to three times — before concluding no mail was sent.',
  inputSchema: z.object({
    address: z.string(),
    subjectContains: z.string().optional(),
    timeoutSeconds: z.number().int().min(5).max(120).default(60),
  }),
  execute: async ({ address, subjectContains = '', timeoutSeconds }) => {
    const deadline = Date.now() + timeoutSeconds * 1000;
    while (Date.now() < deadline) {
      const res = await fetch(`${API}/mailbox?address=${encodeURIComponent(address)}`);
      if (res.status === 429) { await sleep(Number(res.headers.get('retry-after') ?? 1) * 1000); continue; }
      if (!res.ok) throw new Error(`GET /mailbox answered ${res.status}`);
      const { messages } = (await res.json()) as { messages: { id: string; subject: string }[] };
      const hit = messages.find(m => m.subject.toLowerCase().includes(subjectContains.toLowerCase()));
      if (hit) return (await fetch(`${API}/message/${hit.id}?mailbox=${encodeURIComponent(address)}`)).json();
      await sleep(1000);
    }
    return { status: 'timed_out', hint: 'Call again, up to three times, before giving up.' };
  },
});

O formato é idêntico em qualquer framework que tenha uma noção de ferramenta: uma descrição que o modelo lê, um schema para os argumentos, e uma função que roda do seu lado. As duas coisas para levar adiante são a frase sobre o alias e a frase sobre timed_out; tudo o mais é o cliente do guia de Node ou do guia de Python.

O loop, em quatro passos

Seja qual for o framework, um cadastro é sempre as mesmas quatro chamadas de ferramenta, e o agente deveria ser avisado disso nas próprias instruções, em vez de descobrir sozinho:

  1. create_inbox, uma vez por tarefa. Voltam um endereço, um alias e uma frase dizendo qual é qual.
  2. O alias vai para o formulário. Envie.
  3. wait_for_message no endereço, imediatamente, com subject_contains definido para uma palavra que o e-mail de confirmação vai carregar — “code”, “verify”, “confirm”. Não em um timer, não depois de outro trabalho: o e-mail já está a caminho.
  4. O código sai da mensagem que a espera retornou; o agente o digita ou segue o link. Seis dígitos depois das palavras que o template usa — códigos OTP em testes automatizados tem as regras de extração, que se aplicam a um agente exatamente como a um teste.

Uma espera de sessenta segundos que retorna timed_out não é uma falha; é “ainda não”. As instruções deveriam dizer: chame de novo, até três vezes. Três chamadas são mais de três minutos, o que cobre qualquer e-mail transacional que tenha realmente sido enviado — e dá ao agente três chances de perceber que o formulário mostrou um erro, ou que ele digitou o alias errado.

A regra do alias

Toda caixa de entrada tem dois endereços. O endereço é aquele com o qual o agente lê; qualquer um que o tenha pode abrir a caixa de entrada, porque não há conta e o endereço é a única chave. O alias é um segundo endereço em um domínio separado que entrega na mesma caixa de entrada e não pode ser usado para lê-la.

Então o site recebe o alias e o agente guarda o endereço. Um agente que cola o endereço em um formulário entregou ao site — e a qualquer um para quem o site o vazar — a capacidade de ler toda mensagem que o agente algum dia receber ali. As funções de ferramenta acima retornam os dois com um next_step dizendo o que vai para onde, e as instruções repetem isso, porque uma regra dita duas vezes é uma regra seguida.

alias@examplewhat you hand overGrabMailroutes it to the mailboxyou@examplewhat you keepRead it back and you getan empty mailbox. Always.Read it back and you getevery message.The service you signed up to only ever holds the left-hand one.
O alias é o que o agente entrega e, ao consultá-lo, lê uma caixa de entrada vazia; o endereço é o que ele guarda e, ao consultá-lo, lê toda mensagem.

Proteções para um agente sem supervisão

Uma suíte de testes falha e para. Um agente que interpreta mal uma situação continua indo, e continua gastando. Cinco limites impedem que uma etapa de e-mail se torne a parte cara de uma tarefa:

Uma caixa de entrada por tarefa
Nunca reutilize um endereço entre tarefas ou execuções. Uma mensagem antiga com um código de aparência plausível dentro é a forma mais rápida de um agente fazer a coisa errada com total confiança. create_inbox não custa nada; chame-a toda vez.
Um orçamento de esperas
Três chamadas a wait_for_message, depois pare e reporte. Um agente que espera indefinidamente por um e-mail que nunca foi enviado consome um slot de worker e gera custo.
Um prazo para a etapa inteira
Cinco minutos do envio ao código, de ponta a ponta. Além disso, a ação certa é contar a um humano o que aconteceu, não tentar o formulário de novo.
Filtragem por assunto
Sempre passe subject_contains. Um e-mail de boas-vindas que chega antes do e-mail com código é, do contrário, “a mensagem”, e o agente extrai seis dígitos de um rodapé de marketing.
Registrando o endereço no log
Escreva o endereço no log da tarefa. As mensagens ficam 5 dias, então um humano pode abrir a caixa de entrada depois e ver exatamente o que o agente viu — a coisa mais útil quando uma execução dá errado.

Agentes de navegador

Um agente pilotando um navegador de verdade — Browser Use, um servidor MCP do Playwright, um modelo de computer-use — é o caso em que a etapa de e-mail morde mais forte, porque ele vai encontrar o formulário antes de qualquer um ter planejado para isso. Três coisas fazem isso funcionar:

  • Dê a ele os dois servidores. As ferramentas de navegador e as ferramentas de caixa de entrada na mesma sessão, para que “confira seu e-mail” seja uma chamada de ferramenta, e não um beco sem saída.
  • Coloque o loop no system prompt. Quatro linhas: crie uma caixa de entrada no primeiro campo de e-mail; alias no formulário; espere no endereço logo após o envio; três novas tentativas em caso de timed_out.
  • Espere recusas. Um formulário que rejeita o domínio do alias vai dizer isso na página; o agente deveria ler o erro e parar, não tentar outros nomes. As formas honestas de contornar uma recusa — um domínio seu, ou um domínio do conjunto mantido fora das listas de bloqueio — são decisões de configuração para você, não para o agente.

Para a própria leitura do agente, o site publica llms.txt, um mapa em texto puro que diz as mesmas coisas que esta página diz, na forma que um modelo prefere, e um documento OpenAPI a partir do qual um agente que escreve código consegue construir o cliente.

Antes de deixá-lo rodar sem supervisão

  • Descrições de ferramentas que declaram a regra do alias e o que timed_out significa.
  • Instruções com o loop de quatro passos e um orçamento de três esperas.
  • create_inbox chamada uma vez por tarefa, nunca reutilizada.
  • subject_contains em toda espera.
  • O endereço escrito no log da tarefa.
  • Uma linha proibindo que qualquer coisa confidencial seja enviada à caixa de entrada.

Isso é tudo o que um agente precisa. A mesma caixa de entrada serve uma suíte de testes de dia e um agente de noite, porque por baixo são as mesmas três chamadas HTTP — e se o site em que o agente se cadastra recusa os domínios públicos, um domínio seu ou o conjunto mantido fora das listas se encaixa sem nenhuma mudança nas ferramentas.

Perguntas

Preciso de uma chave de API para o agente?

Não. Os domínios públicos não exigem chave, conta nem cabeçalho, tanto via REST quanto via MCP. Só o conjunto pago de domínios mantidos fora das listas de bloqueio de e-mail descartável usa um token bearer, e fora isso as funções de ferramenta são idênticas para ele.

MCP ou uma ferramenta REST — qual devo escolher?

Se o agente já vive em um cliente MCP, MCP: é uma linha e a espera é do lado do servidor. Se você está escrevendo o agente em um framework, uma função de ferramenta: são quarenta linhas, funciona com qualquer modelo, e você controla a descrição que o modelo lê. Os dois alcançam a mesma caixa de entrada.

Quanto a espera custa em tokens?

Nada, de qualquer forma. A espera via MCP bloqueia no servidor; a ferramenta REST faz o loop no seu código. O modelo gasta tokens na chamada de ferramenta e na leitura do resultado, não nos sessenta segundos entre elas — e essa é a razão inteira para não deixar um modelo fazer polling em uma caixa de entrada sozinho.

Vários agentes podem rodar ao mesmo tempo?

Sim. Cada tarefa recebe sua própria caixa de entrada e não há estado de sessão. Os limites são uma leitura por segundo por endereço e 1200 requisições por minuto por cliente via REST, e 8 chamadas simultâneas de wait_for_message via MCP — além disso a ferramenta responde timed_out na hora e o agente chama de novo.

O agente pode enviar e-mail a partir da caixa de entrada?

Não. O serviço só recebe, por design — uma caixa de entrada gratuita sem conta que pudesse enviar viraria um relay de spam em menos de uma hora. Um agente que precisa enviar e-mail precisa de um provedor de envio e das próprias credenciais.

E se o site recusar o domínio do alias?

Então ele está em uma lista de bloqueio de domínios descartáveis, e nenhum nome na frente do @ vai mudar isso. Aponte um domínio seu para o serviço (um registro MX, gratuito), ou use um domínio do conjunto pago mantido fora das listas — os dois se encaixam nas mesmas ferramentas mudando a constante do domínio. Por que formulários de cadastro bloqueiam e-mail descartável explica qual verificação te recusou.

A caixa de entrada é privada para o meu agente?

Não. Qualquer um que saiba o endereço pode lê-la, e é por isso que o alias existe e por isso que nada confidencial deveria jamais ser enviado para lá. Para um código que vive dez minutos, isso não é problema; é a única regra que as instruções do agente precisam declarar claramente.

Experimente enquanto ainda está fresco

Um endereço leva um clique, sem conta e sem cartão. Tudo neste guia funciona nele imediatamente.

Bem-vindo de volta

As suas caixas e os seus domínios, num só lugar.