افتح صندوق بريد

وكلاء الذكاء الاصطناعي وMCP

بريد لوكلاء الذكاء الاصطناعي: تحقق من التسجيل بلا بشر

الوكيل القادر على التصفح وملء النماذج والدفع يتوقف تمامًا عند «تحقق من بريدك الإلكتروني للحصول على الرمز»، لأنه لا يملك صندوق وارد. وهذا شرح لكيفية إعطائه صندوقًا لا يحتاج إلى حساب ولا مفتاح — عبر MCP بانتظار حاجب، أو كدالة أداة بسيطة لـLangChain وOpenAI Agents SDK وVercel AI SDK — مع حلقة الخطوات الأربع التي تُتمّ عملية تسجيل، وضوابط الأمان التي يحتاجها وكيل يعمل دون إشراف.

  • متوسط
  • 21 دقيقةً للقراءة
روبوت رمادي صغير عند مكتب رمادي يرفع مظروفًا أزرق، ويده الأخرى على لوحة مفاتيح، وفوقه علامة صح زرقاء تطفو

الجدار الذي يصطدم به كل وكيل

التسجيل في تجربة، أو إنشاء مساحة عمل، أو الحصول على مفتاح API، أو الانضمام إلى نسخة تجريبية: كل واحدة من هذه المهام تنتهي عند نموذج، وكل نموذج ينتهي عند صندوق بريد. الإنسان يلقي نظرة على هاتفه. أما الوكيل فليس لديه ما ينظر إليه — فلا عنوان لديه يستطيع قراءته، والوكلاء العقلاء يتوقفون ويطلبون منك الرمز، وهذا يُبطل الغرض من إرسال وكيل أصلاً.

والحل ليس أمرًا (prompt) أذكى. بل صندوق وارد يستطيع الوكيل قراءته برمجيًا، بلا حساب يجب إنشاؤه أولاً (فالوكيل الذي ينشئ حساب بريد يصطدم بالجدار نفسه في مستوى أدنى) وبلا مفتاح يجب إدارته. وصندوق البريد المؤقت هو ذلك بالضبط: يوجد صندوق البريد لحظة وصول البريد إليه، وقراءته طلب HTTP واحد.

طريقتان لإعطائه صندوق وارد

يمكن الوصول إلى صندوق البريد نفسه بطريقتين، ويعتمد الاختيار على كيفية بناء الوكيل لا على صندوق البريد:

الخاصيةخادم MCPدالة أداة REST
يناسب حينيعمل الوكيل داخل عميل MCP — Claude Code، أو Cursor، أو Claude Desktop، أو إطار عمل فيه مهايئ MCP.تكتب الوكيل بنفسك في الكود: LangChain، أو OpenAI Agents SDK، أو Vercel AI SDK، أو حلقتك الخاصة.
الانتظارwait_for_message تنتظر من جهة الخادم حتى 25 ثانية وتعيد الرسالة كاملة. ولا تُستهلَك أي tokens أثناء الانتظار.تكرر دالة الأداة التحقق مرة كل ثانية حتى موعدها النهائي. ولا تُستهلَك tokens هنا أيضًا — فالحلقة في كودك أنت، لا في النموذج.
الإعدادرابط واحد في إعداد العميل. بلا كود.دالتان، وأربعون سطرًا، ومكتبة HTTP واحدة.
ما الذي يراه النموذجست أدوات بأوصافها، إضافة إلى فقرة تعليمات يرسلها الخادم وقت الاتصال.أيًا كان ما تقوله أوصاف أدواتك. وقد كُتبت الـdocstrings أدناه لتكون هي تلك الأوصاف.

مسار MCP سطر واحد، ويحتوي دليل الإعداد الخاص بكل عميل على السطر الدقيق لسبعة عملاء؛ ويشرح دليل صندوق وارد يستطيع وكيل ذكاء اصطناعي قراءته الأدوات بتعمق. وبقية هذا الدليل هي مسار REST، للوكلاء الذين تبنيهم بنفسك.

مسار MCP كاملاً
{"mcpServers":{"grabmail":{"url":"https://grabmail.io/mcp"}}}

دالة أداة: بلغة Python، لـLangChain وOpenAI Agents SDK

دالتان بسيطتان بـdocstrings دقيقة الصياغة. وهذه الـdocstrings أهم من الكود نفسه: ففي كلا الإطارين تصبح هي الوصف الذي يقرؤه النموذج ليقرر متى يستدعي الأداة وماذا يفعل بالإجابة، لذا فهي تقول الأمرين اللذين يخطئ فيهما الوكيل — المستعار مقابل العنوان، ومعنى timed_out.

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."}

وتغليفهما استدعاء واحد لكل إطار عمل. فدالة tool في LangChain تقرأ الـdocstring وتلميحات النوع؛ وتفعل function_tool في Agents SDK الأمر نفسه وتضيف الأدوات إلى وكيل تكرر تعليماته الحلقة:

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)

الأداة نفسها بلغة TypeScript، لـVercel AI SDK

تأخذ tool() في AI SDK وصفًا، وschema، ودالة execute؛ ويحمل الوصف الجملتين نفسهما. مرِّر كلتا الأداتين إلى generateText أو streamText مع maxSteps أعلى من أربعة، لأن طول الحلقة أربعة استدعاءات أدوات:

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.' };
  },
});

والشكل مطابق في أي إطار عمل لديه مفهوم للأداة: وصف يقرؤه النموذج، وschema للوسائط، ودالة تعمل من جهتك أنت. والأمران اللذان يجب نقلهما هما جملة المستعار وجملة timed_out؛ وكل ما عداهما هو العميل من دليل Node أو دليل Python.

الحلقة، في أربع خطوات

أيًا كان إطار العمل، فالتسجيل هو استدعاءات الأدوات الأربعة نفسها، وينبغي إخبار الوكيل بذلك في تعليماته بدل تركه يكتشفها بنفسه:

  1. create_inbox، مرة واحدة لكل مهمة. فيعود عنوان، ومستعار، وجملة توضح أيهما أيهما.
  2. المستعار يذهب إلى النموذج. أرسِله.
  3. wait_for_message على العنوان، فورًا، مع ضبط subject_contains على كلمة ستحملها رسالة التأكيد — «code» أو «verify» أو «confirm». لا بعد مؤقِّت، ولا بعد أعمال أخرى: فالبريد في طريقه بالفعل.
  4. يخرج الرمز من الرسالة التي أعادها الانتظار؛ فيكتبه الوكيل أو يتبع الرابط. وعادةً ستة أرقام بعد الكلمات التي يستخدمها القالب — ويحتوي دليل رموز OTP في الاختبارات الآلية على قواعد الاستخراج، وهي تنطبق على الوكيل تمامًا كما تنطبق على اختبار.

انتظار ستين ثانية ينتهي بـtimed_out ليس فشلاً؛ إنه «ليس بعد». وينبغي أن تقول التعليمات: استدعِ مرة أخرى، حتى ثلاث مرات. وثلاثة استدعاءات تعني أكثر من ثلاث دقائق، وهذا يغطي أي بريد معاملاتي أُرسل فعلاً — ويمنح الوكيل ثلاث فرص لملاحظة أن النموذج أظهر خطأً، أو أنه كتب المستعار خطأً.

قاعدة المستعار

لكل صندوق بريد عنوانان. العنوان هو الذي يقرأ به الوكيل؛ ويستطيع أي شخص يملكه فتح صندوق البريد، لأنه لا يوجد حساب والعنوان هو المفتاح الوحيد. أما المستعار فهو عنوان ثانٍ على نطاق منفصل يسلِّم إلى الصندوق نفسه ولا يمكن استخدامه لقراءته.

لذا يحصل الموقع على المستعار ويحتفظ الوكيل بالعنوان. والوكيل الذي يلصق العنوان في نموذج يكون قد سلَّم الموقع — وأي جهة يسرّبه إليها الموقع — القدرة على قراءة كل رسالة سيتلقاها الوكيل هناك على الإطلاق. وتعيد دالتا الأداة أعلاه كليهما مع حقل next_step يوضح أيهما يذهب إلى أين، وتكرر التعليمات ذلك، لأن القاعدة المذكورة مرتين قاعدة تُتَّبع.

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.
المستعار هو ما يوزِّعه الوكيل ويقرأ منه صندوق بريد فارغًا؛ والعنوان هو ما يحتفظ به ويقرأ منه كل رسالة.

ضوابط أمان لوكيل يعمل دون إشراف

مجموعة الاختبارات تفشل وتتوقف. أما الوكيل الذي يسيء قراءة موقف ما فيواصل العمل، ويواصل الإنفاق. وخمسة حدود تمنع خطوة البريد الإلكتروني من أن تصبح الجزء الأغلى من المهمة:

صندوق وارد واحد لكل مهمة
لا تُعِد استخدام عنوان أبدًا عبر مهام أو تشغيلات مختلفة. فرسالة قديمة تحمل رمزًا يبدو معقولاً هي أسرع طريقة لوكيل ليفعل الشيء الخطأ بثقة تامة. create_inbox لا تكلِّف شيئًا؛ استدعِها في كل مرة.
ميزانية لعمليات الانتظار
ثلاثة استدعاءات لـwait_for_message، ثم توقف وأبلِغ. فالوكيل الذي ينتظر إلى ما لا نهاية بريدًا لم يُرسَل أبدًا يستهلك فتحة عامل وفاتورة.
موعد نهائي للخطوة كلها
خمس دقائق من الإرسال إلى الرمز، من البداية إلى النهاية. وبعد تجاوزها، فالتصرف الصحيح هو إخبار إنسان بما حدث، لا إعادة محاولة النموذج مرة أخرى.
ترشيح الموضوع
مرِّر subject_contains دائمًا. وإلا فإن رسالة ترحيب تصل قبل رسالة الرمز تصبح هي «الرسالة»، ويستخرج الوكيل ستة أرقام من تذييل تسويقي.
تسجيل العنوان في السجلّ
اكتب العنوان في سجلّ المهمة. وتبقى الرسائل 5 يومًا، بحيث يستطيع إنسان فتح صندوق البريد لاحقًا ورؤية ما رآه الوكيل بالضبط — وهذا أفيد شيء على الإطلاق حين تسوء تشغيلة ما.

وكلاء المتصفح

الوكيل الذي يقود متصفحًا حقيقيًا — Browser Use، أو خادم Playwright MCP، أو نموذج computer-use — هو الحالة التي تؤلم فيها خطوة البريد الإلكتروني أكثر ما يكون، لأنه سيواجه النموذج قبل أن يخطط أحد لذلك. وثلاثة أمور تجعله يعمل:

  • أعطه الخادمين كليهما. أدوات المتصفح وأدوات صندوق الوارد في الجلسة نفسها، بحيث تصبح «تحقق من بريدك» استدعاء أداة لا طريقًا مسدودًا.
  • ضع الحلقة في أمر النظام (system prompt). أربعة أسطر: أنشئ صندوق واردٍ عند أول حقل بريد إلكتروني؛ وضع المستعار في النموذج؛ وانتظر على العنوان فور الإرسال؛ وثلاث محاولات إعادة عند timed_out.
  • توقَّع الرفض. فالنموذج الذي يرفض نطاق المستعار سيقول ذلك في الصفحة؛ وينبغي أن يقرأ الوكيل الخطأ ويتوقف، لا أن يجرّب أسماء أخرى. والطرق الصادقة للالتفاف على الرفض — نطاق تملكه أنت، أو نطاق من المجموعة التي تُبقى بعيدة عن قوائم الحظر — قرارات إعداد تخصك أنت، لا الوكيل.

ولقراءة الوكيل نفسه، ينشر الموقع llms.txt، وهو خريطة نصية عادية تقول الأشياء نفسها التي تقولها هذه الصفحة بالصيغة التي يفضلها النموذج، ووثيقة OpenAPI يستطيع وكيل يكتب الكود بناء العميل منها.

قبل أن تدعه يعمل دون إشراف

  • أوصاف أدوات تذكر قاعدة المستعار ومعنى timed_out.
  • تعليمات فيها حلقة الخطوات الأربع وميزانية ثلاث محاولات انتظار.
  • استدعاء create_inbox مرة واحدة لكل مهمة، دون إعادة استخدام أبدًا.
  • subject_contains في كل عملية انتظار.
  • العنوان مكتوب في سجلّ المهمة.
  • سطر يمنع إرسال أي شيء سرّي إلى صندوق الوارد.

هذا كل ما يحتاجه الوكيل. ويخدم صندوق البريد نفسه مجموعة اختبارات نهارًا ووكيلاً ليلاً، لأنه من الداخل استدعاءات HTTP الثلاثة نفسها — وإذا رفض الموقع الذي يسجِّل فيه الوكيل النطاقات العامة، فإن نطاقًا تملكه أنت أو المجموعة التي تُبقى بعيدة عن القوائم يُدرَج دون أي تغيير في الأدوات.

أسئلة

هل أحتاج إلى مفتاح API للوكيل؟

لا. فالنطاقات العامة لا تتطلب مفتاحًا ولا حسابًا ولا ترويسة، عبر REST وMCP على حد سواء. ولا تستخدم رمز حامل (bearer token) إلا مجموعة النطاقات المدفوعة التي تُبقى بعيدة عن قوائم حظر البريد المؤقت، ودوال الأداة مطابقة لها فيما عدا ذلك.

MCP أم أداة REST — أيهما أختار؟

إذا كان الوكيل يعمل أصلاً داخل عميل MCP، فاختر MCP: فهو سطر واحد والانتظار من جهة الخادم. وإذا كنت تكتب الوكيل في إطار عمل، فاختر دالة أداة: فهي أربعون سطرًا، وتعمل مع أي نموذج، وتتحكم أنت في الوصف الذي يقرؤه النموذج. وكلاهما يصل إلى صندوق البريد نفسه.

كم تكلِّف عملية الانتظار من tokens؟

لا شيء، في كلتا الحالتين. فانتظار MCP يحدث من جهة الخادم؛ وأداة REST تكرر التحقق في كودك أنت. وينفق النموذج tokens على استدعاء الأداة وعلى قراءة النتيجة، لا على الستين ثانية بينهما — وهذا هو السبب الكامل وراء عدم ترك النموذج يستطلع صندوق بريد بنفسه.

هل يمكن لعدة وكلاء العمل في الوقت نفسه؟

نعم. فلكل مهمة صندوق واردها الخاص ولا توجد حالة جلسة. والحدود هي قراءة واحدة في الثانية لكل عنوان و1200 طلب في الدقيقة لكل عميل عبر REST، و8 من استدعاءات wait_for_message المتزامنة عبر MCP — وبعد تجاوز ذلك تجيب الأداة فورًا بـtimed_out ويستدعيها الوكيل مرة أخرى.

هل يستطيع الوكيل إرسال بريد من صندوق الوارد؟

لا. فالخدمة تستقبل فقط، بحكم التصميم — فأي صندوق وارد مجاني بلا حساب يستطيع الإرسال سيتحول إلى مُرحِّل بريد مزعج خلال ساعة واحدة. والوكيل الذي عليه إرسال بريد يحتاج إلى مزوّد إرسال وبيانات اعتماد خاصة به.

ماذا لو رفض الموقع نطاق المستعار؟

فهذا يعني أنه مدرج في قائمة حظر نطاقات مؤقتة، ولن يغيّر أي اسم أمام الـ@ ذلك. وجِّه نطاقًا تملكه إلى الخدمة (سجل MX واحد، مجانًا)، أو استخدم نطاقًا من المجموعة المدفوعة التي تُبقى بعيدة عن القوائم — وكلاهما يُدرَج في الأدوات نفسها بتغيير ثابت النطاق فقط. ويشرح دليل لماذا تحظر نماذج التسجيل البريد المؤقت أي فحص هو الذي رفضك.

هل صندوق الوارد خاص بوكيلي؟

لا. فأي شخص يعرف العنوان يستطيع قراءته، ولهذا يوجد المستعار، ولهذا لا ينبغي إرسال أي شيء سرّي إلى هناك أبدًا. وهذا أمر مقبول لرمز يعيش عشر دقائق؛ وهي القاعدة الوحيدة التي يجب أن تنص عليها تعليمات الوكيل بوضوح.

تابع القراءة

جرّبه وهو جديد

عنوان واحد يستغرق نقرة واحدة، بلا حساب وبلا بطاقة. كل ما في هذا الدليل يعمل عليه فورًا.

أهلًا بعودتك

صناديقك ونطاقاتك في مكان واحد.