Tembok yang ditabrak setiap agen
Mendaftar uji coba, membuat sebuah workspace, mengambil sebuah kunci API, bergabung dengan sebuah beta: masing-masing berakhir di sebuah formulir, dan setiap formulir berakhir di sebuah kotak surat. Manusia melirik ponselnya. Seorang agen tidak punya apa pun untuk dilirik — ia tidak punya alamat yang bisa dibacanya, dan agen yang cukup masuk akal akan berhenti dan meminta kodenya ke Anda, yang justru menggagalkan keseluruhan alasan mengirim seorang agen.
Solusinya bukan sebuah prompt yang lebih pintar. Solusinya adalah sebuah kotak masuk yang bisa dibaca agennya secara programatik, tanpa akun yang perlu dibuat lebih dulu (seorang agen yang membuat akun mail akan menabrak tembok yang sama, selapis lebih dalam) dan tanpa kunci yang perlu dikelola. Sebuah kotak surat sekali pakai persis seperti itu: kotak suratnya ada begitu email mencapainya, dan membacanya cukup satu request HTTP.
Dua cara memberinya sebuah kotak masuk
Kotak surat yang sama bisa dijangkau lewat dua cara, dan pilihannya tergantung pada bagaimana agennya dibangun, bukan pada kotak suratnya:
| Kriteria | Server MCP | Fungsi tool REST |
|---|---|---|
| Cocok saat | Agennya berjalan di dalam sebuah client MCP — Claude Code, Cursor, Claude Desktop, atau sebuah framework dengan adapter MCP. | Anda menulis agennya dalam kode: LangChain, OpenAI Agents SDK, Vercel AI SDK, atau loop buatan Anda sendiri. |
| Penantian | wait_for_message blocking di sisi server sampai 25 detik dan mengembalikan pesannya secara utuh. Nol token terpakai selama menunggu. | Fungsi tool-nya melakukan loop sekali per detik sampai batas waktunya. Nol token juga — loop-nya ada di dalam kode Anda, bukan di dalam model. |
| Pengaturan | Satu URL di dalam konfigurasi client-nya. Tanpa kode. | Dua fungsi, empat puluh baris, satu library HTTP. |
| Apa yang dilihat model-nya | Enam tool dengan deskripsinya, ditambah sebuah paragraf instruksi yang dikirim server-nya saat koneksi terjadi. | Apa pun yang dikatakan deskripsi tool Anda. Docstring di bawah ini ditulis untuk menjadi deskripsi itu sendiri. |
Jalur MCP-nya cuma satu baris, dan pengaturan per-client memuat baris persisnya untuk tujuh client; kotak masuk yang bisa dibaca agen AI menjelaskan tool-toolnya secara mendalam. Sisa panduan ini adalah jalur REST-nya, untuk agen yang Anda bangun sendiri.
{"mcpServers":{"grabmail":{"url":"https://grabmail.io/mcp"}}}Sebuah fungsi tool: Python, untuk LangChain dan OpenAI Agents SDK
Dua fungsi biasa dengan docstring yang ditulis dengan cermat. Docstring-nya lebih penting daripada kodenya: di kedua framework, docstring itu menjadi deskripsi yang dibaca model untuk memutuskan kapan memanggil tool-nya dan apa yang harus dilakukan dengan jawabannya, jadi docstring-nya menyebutkan dua hal yang paling sering salah dilakukan seorang agen — alias versus alamat, dan apa arti timed_out.
"""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."}Membungkusnya cukup satu panggilan per framework. tool milik LangChain membaca docstring dan type hints-nya; function_tool milik Agents SDK melakukan hal yang sama dan menambahkan tool-toolnya ke sebuah agen yang instruksinya mengulang loop tersebut:
# 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: 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)Tool yang sama dalam TypeScript, untuk Vercel AI SDK
tool() milik AI SDK menerima sebuah description, sebuah schema, dan sebuah execute; description-nya membawa dua kalimat yang sama. Sertakan kedua tool itu ke generateText atau streamText dengan maxSteps di atas empat, karena loop-nya panjangnya empat panggilan tool:
// 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.' };
},
});Bentuknya identik di framework mana pun yang punya konsep sebuah tool: sebuah description yang dibaca model, sebuah schema untuk argumennya, dan sebuah fungsi yang berjalan di sisi Anda. Dua hal yang harus dibawa serta adalah kalimat aliasnya dan kalimat timed_out-nya; semua yang lain adalah client dari panduan Node atau panduan Python.
Loop-nya, dalam empat langkah
Framework apa pun itu, sebuah pendaftaran adalah empat panggilan tool yang sama, dan agennya sebaiknya diberi tahu soal ini di dalam instruksinya, bukan dibiarkan menemukannya sendiri:
create_inbox, sekali per tugas. Yang kembali adalah sebuah alamat, sebuah alias, dan sebuah kalimat yang menjelaskan mana yang mana.- Aliasnya masuk ke dalam formulir. Kirim.
wait_for_messagepada alamatnya, segera, dengansubject_containsdiatur ke sebuah kata yang akan dibawa email konfirmasinya — “code”, “verify”, “confirm”. Bukan berdasarkan timer, bukan setelah pekerjaan lain: emailnya sudah dalam perjalanan.- Kodenya diambil dari pesan yang dikembalikan penantian itu; agennya mengetikkannya atau mengikuti tautannya. Enam digit setelah kata-kata yang dipakai template-nya — kode OTP dalam pengujian otomatis memuat aturan ekstraksinya, yang berlaku untuk seorang agen persis seperti untuk sebuah pengujian.
Sebuah penantian enam puluh detik yang mengembalikan timed_out bukan sebuah kegagalan; itu berarti “belum”. Instruksinya harus bilang: panggil lagi, sampai tiga kali. Tiga panggilan sudah lebih dari tiga menit, dan itu menutupi email transaksional apa pun yang benar-benar terkirim — dan memberi agennya tiga kesempatan untuk menyadari bahwa formulirnya menunjukkan error, atau bahwa ia salah ketik aliasnya.
Aturan aliasnya
Setiap kotak surat punya dua alamat. Alamatnya adalah yang dipakai agen untuk membaca; siapa pun yang memilikinya bisa membuka kotak suratnya, karena tidak ada akun dan alamatnya adalah satu-satunya kunci. Aliasnya adalah alamat kedua pada domain terpisah yang mengantarkan surat ke kotak surat yang sama dan tidak bisa dipakai untuk membacanya.
Jadi situsnya mendapat aliasnya dan agennya menyimpan alamatnya. Agen yang menempelkan alamatnya ke dalam sebuah formulir telah memberi situs itu — dan siapa pun yang dibocorkan situs itu — kemampuan untuk membaca setiap pesan yang akan pernah diterima agennya di sana. Fungsi-fungsi tool di atas mengembalikan keduanya dengan sebuah next_step yang menjelaskan mana yang ke mana, dan instruksinya mengulang itu, karena aturan yang disebutkan dua kali adalah aturan yang dipatuhi.
Guardrail untuk agen yang berjalan tanpa pengawasan
Sebuah test suite gagal lalu berhenti. Seorang agen yang salah membaca sebuah situasi terus berjalan, dan terus menghabiskan biaya. Lima batasan berikut menjaga agar langkah email tidak menjadi bagian termahal dari sebuah tugas:
- Satu kotak masuk per tugas
- Jangan pernah memakai ulang satu alamat lintas tugas atau lintas run. Sebuah pesan lama dengan kode yang terlihat masuk akal di dalamnya adalah cara tercepat bagi seorang agen untuk dengan percaya diri melakukan hal yang salah.
create_inboxtidak ada biayanya; panggil setiap saat. - Anggaran penantian
- Tiga panggilan ke
wait_for_message, lalu berhenti dan laporkan. Seorang agen yang menunggu tanpa batas waktu untuk sebuah email yang tidak pernah terkirim menghabiskan sebuah worker slot dan biaya. - Sebuah batas waktu untuk keseluruhan langkah
- Lima menit dari pengiriman formulir sampai kode, dari ujung ke ujung. Melewati itu, tindakan yang tepat adalah memberi tahu seorang manusia apa yang terjadi, bukan mencoba formulirnya lagi.
- Filter subjek
- Selalu sertakan
subject_contains. Email sambutan yang tiba sebelum email kode akan jadi “pesannya” kalau tidak, dan agennya akan mem-parse enam digit dari sebuah footer pemasaran. - Mencatat alamatnya di log
- Tulis alamatnya ke dalam log tugas. Pesan-pesannya bertahan 5 hari, sehingga seorang manusia bisa membuka kotak suratnya belakangan dan melihat persis apa yang dilihat agennya — satu hal paling berguna saat sebuah run berjalan salah.
Agen browser
Seorang agen yang mengendalikan browser sungguhan — Browser Use, sebuah server MCP Playwright, sebuah model computer-use — adalah kasus di mana langkah email ini paling terasa menggigit, karena ia akan bertemu formulirnya sebelum ada yang sempat merencanakannya. Tiga hal yang membuatnya berhasil:
- Berikan ia kedua server itu. Tool browser dan tool kotak masuk dalam sesi yang sama, sehingga “cek email Anda” menjadi sebuah panggilan tool, bukan jalan buntu.
- Taruh loop-nya di dalam system prompt. Empat baris: buat sebuah kotak masuk di kolom email pertama; masukkan aliasnya ke dalam formulir; tunggu pada alamatnya tepat setelah dikirim; tiga kali retry saat
timed_out. - Perkirakan akan ada penolakan. Sebuah formulir yang menolak domain aliasnya akan menyebutkan itu di halamannya; agennya harus membaca error itu dan berhenti, bukan mencoba nama-nama lain. Cara-cara jujur untuk mengatasi sebuah penolakan — domain milik Anda sendiri, atau domain dari kumpulan yang dijaga agar tidak masuk daftar blokir — adalah keputusan konfigurasi untuk Anda, bukan untuk agennya.
Untuk dibaca oleh agennya sendiri, situs ini mempublikasikan llms.txt, sebuah peta teks polos yang mengatakan hal yang sama seperti halaman ini dalam bentuk yang lebih disukai sebuah model, dan sebuah dokumen OpenAPI yang bisa dipakai seorang agen penulis kode untuk membangun client-nya.
Sebelum Anda membiarkannya berjalan tanpa pengawasan
- Deskripsi tool yang menyatakan aturan aliasnya dan apa arti
timed_out. - Instruksi dengan loop empat langkah dan anggaran tiga kali penantian.
create_inboxdipanggil sekali per tugas, jangan pernah dipakai ulang.subject_containsdi setiap penantian.- Alamatnya tercatat di log tugas.
- Sebuah baris yang melarang apa pun yang bersifat rahasia dikirim ke kotak masuknya.
Itulah semua yang dibutuhkan seorang agen. Kotak masuk yang sama melayani sebuah test suite di siang hari dan seorang agen di malam hari, karena di baliknya adalah tiga panggilan HTTP yang sama — dan jika situs tempat agennya mendaftar menolak domain publiknya, domain milik Anda sendiri atau kumpulan yang dijaga agar tidak masuk daftar-daftar itu langsung bisa dipasang tanpa perubahan apa pun pada tool-toolnya.
Pertanyaan
Apakah saya butuh kunci API untuk agennya?
Tidak. Domain publiknya tidak butuh kunci, tidak butuh akun, dan tidak butuh header apa pun, baik lewat REST maupun lewat MCP. Hanya kumpulan domain berbayar yang dijaga agar tidak masuk daftar blokir email sekali pakai yang memakai bearer token, dan selain itu fungsi tool-nya identik saja.
MCP atau tool REST — mana yang sebaiknya saya pilih?
Jika agennya sudah berada di dalam sebuah client MCP, pilih MCP: cuma satu baris dan penantiannya di sisi server. Jika Anda menulis agennya di dalam sebuah framework, pilih fungsi tool: cuma empat puluh baris, berfungsi dengan model apa pun, dan Anda mengendalikan deskripsi yang dibaca model itu. Keduanya menjangkau kotak surat yang sama.
Berapa biaya penantiannya dalam token?
Tidak ada biayanya, dengan cara mana pun. Penantian MCP-nya blocking di server; tool REST-nya melakukan loop di dalam kode Anda. Model-nya menghabiskan token pada panggilan tool-nya dan pada membaca hasilnya, bukan pada enam puluh detik di antaranya — dan itulah keseluruhan alasan untuk tidak membiarkan sebuah model melakukan polling ke kotak surat sendiri.
Bisakah beberapa agen berjalan sekaligus?
Bisa. Setiap tugas mendapat kotak masuknya sendiri dan tidak ada session state. Batasannya adalah satu kali baca per detik per alamat dan 1200 request per menit per klien lewat REST, dan 8 panggilan wait_for_message yang berjalan bersamaan lewat MCP — melewati itu, tool-nya langsung menjawab timed_out dan agennya memanggil lagi.
Bisakah agennya mengirim email dari kotak masuknya?
Tidak. Layanan ini hanya menerima, dan itu memang disengaja — sebuah kotak masuk gratis tanpa akun yang bisa mengirim akan berubah menjadi relai spam dalam hitungan jam. Agen yang harus mengirim email butuh sebuah provider pengirim dan kredensialnya sendiri.
Bagaimana jika situsnya menolak domain aliasnya?
Berarti domain itu ada di dalam sebuah daftar blokir domain sekali pakai, dan nama apa pun di depan @ tidak akan mengubah itu. Arahkan domain milik Anda sendiri ke layanan ini (satu record MX, gratis), atau pakai domain dari kumpulan berbayar yang dijaga agar tidak masuk daftar-daftar itu — keduanya langsung terpasang ke tool yang sama hanya dengan mengubah konstanta domainnya. Mengapa formulir pendaftaran memblokir email sekali pakai menjelaskan pemeriksaan mana yang menolak Anda.
Apakah kotak masuknya bersifat privat untuk agen saya?
Tidak. Siapa pun yang tahu alamatnya bisa membacanya, itulah sebabnya aliasnya ada dan itulah sebabnya tidak ada apa pun yang bersifat rahasia yang boleh dikirim ke sana. Untuk sebuah kode yang hanya hidup sepuluh menit, itu baik-baik saja; itulah satu aturan yang harus dinyatakan secara gamblang di dalam instruksi agennya.


