Everyone obsesses over the prompt. They fine-tune the greeting, tweak the tone, and worry whether the model sounds warm enough. That is a distraction. When an AI agent starts sending real emails to real users, the danger is not that it writes "Best regards" instead of "Cheers." The danger is that you cannot tell, with certainty, what happened between the agent's decision and the message landing in an inbox. I look at the boundary first. That is where production systems quietly die.
The Contract Is the Weak Point
AI demos are forgiving. A smooth conversation in a browser window hides a mess of assumptions. In production, the real vulnerability sits at the contract between three things: the agent's decision, the tool that executes the action, and the step that verifies the result. If that boundary is blurry, the system works beautifully right up until it does not. Then it fails silently, sends duplicates to an entire customer segment, or fires off messages at the wrong time with no clear record of why. The prompt might read like poetry. The architecture underneath can still be held together with string.
Stop Letting the Agent Write Freely
The most common mistake is giving the agent a blank page. Teams let it describe an email in raw text and then trust a downstream tool to parse intent from prose. That is brittle. An LLM might suggest a reasonable intent, but your infrastructure does not need creativity. It needs a contract. It needs specific fields that a machine can validate without ambiguity.
When an agent emits an email request, the output should carry exactly what the plumbing requires:
- Template version: Which version of the email body is being used, so you know what the user saw.
- Recipient scope: Who gets this, defined by user IDs or segment rules, not by natural language like "the user who just signed up."
- Trace ID: A unique identifier that follows this request from the agent through your executor, through the email provider, and into your logs.
- Time window: When this send is valid, so stale agent decisions do not trigger midnight emails hours later.
- Idempotency: A key that prevents the same logical send from firing twice if the agent retries or the network hiccups.
Raw text is a terrible API. It leaves room for ambiguity about urgency, audience, and action. Specific fields are machine-readable, auditable, and testable. They turn a vague instruction into a verifiable command.
Actions, Not Prose
Instead of handing the agent an open-ended writing task, restrict it to a menu of allowed actions. Think of it like an internal API with a fixed enum. The agent does not draft a subject line or wonder about salutations. It chooses an action such as send_review_request or send_retry_notice. That is the extent of its creative freedom.
A deterministic executor then takes that action key, pulls the correct template from version control, hydrates it with sanitized data, fills the recipient list from a verified source, and builds the final command. The agent decides what needs to happen. Boring, predictable code decides how it happens.
This separation makes the system easy to test. You can verify that a given input state reliably triggers send_retry_notice without running an LLM inference at all. Your unit tests become fast and deterministic because they check mapping logic, not model temperature. Your integration tests focus on whether the executor maps the action correctly to the email service, not whether the model was having a good day.
Build in Five Layers
A solid system does not emerge from a single prompt. It is built in layers, and each layer owns a single, clear responsibility.
1. The backend reduces the event to safe data.
Whether the trigger is a webhook, a database change, or a scheduled job, this layer sanitizes inputs, strips unexpected fields, and hands the agent only what it needs. If a webhook payload contains twenty fields but the agent only needs two, pass the two. No raw user text should reach the decision layer unchecked.
2. The agent picks an action from the fixed schema.
It sees the context, makes a judgment call, and outputs one of the predetermined action keys along with the required metadata. It does not draft prose. It does not guess at recipients. It returns a structured payload that the next layer can validate against a JSON schema.
۳. ابزار، مجوزها و فیلدهای مورد نیاز را اعتبارسنجی میکند.
آیا این بافت (context) عامل (agent) حق فراخوانی send_review_request را برای این کاربر دارد؟ آیا محدوده گیرنده (recipient scope) غیرخالی و در محدوده مجاز است؟ آیا کلید همارزی (idempotency key) در لاگ شما موجود و منحصربهفرد است؟ آیا شناسه ردگیری (trace ID) ساختار درستی دارد؟ در اینجا، پیش از آنکه حتی با سرویس ایمیل تماسی برقرار شود، با صراحت خطا را اعلام کنید.
۴. سرویس ایمیل، ارسال را با یک شناسه ردگیری (trace ID) ثبت میکند.
هر پیامی که از سیستم شما خارج میشود، باید آن شناسه ردگیری را از طریق API ارائهدهنده تا پشتهی مشاهدهپذیری (observability stack) شما با خود حمل کند. اگر کاربری شکایت کرد که دو نسخه از پیام را دریافت کرده است، شما باید بتوانید با پرسوجو (query) از یک شناسه، دقیقاً متوجه شوید که منشأ این تکرار کجاست: یک فراخوانی مجدد توسط عامل، یک مجری (executor) ناپایدار، یا یک فراخوانی بازگشتی (callback) دچار مشکل.
۵. تست سرتاسری (end-to-end)، صندوق ورودی واقعی را از نظر محتوا و اثر بررسی میکند.
پیام رندر شده را در یک صندوق ورودی واقعی باز کنید. آیا خط موضوع (subject line) بهدرستی پر شده است؟ آیا لینک لغو اشتراک (unsubscribe) کار میکند؟ آیا کلیک بر روی دکمه اصلی فراخوان (call-to-action) کاربر را به صفحه صحیح با وضعیت صحیح کاربر هدایت میکند؟ موفقیت در یک تست واحد (unit test) فقط به این معناست که کد اجرا شده است. تنها تست صندوق ورودی است که به شما میگوید ایمیل واقعاً برای یک انسان کار میکند.
شواهد به جای حدس و گمان
وقتی تستی در این خط لوله (pipeline) شکست میخورد، شما به چهار قطعه شواهد مشخص نیاز دارید. به کمتر از این رضایت نکنید.
- تصمیم اصلی عامل. چه اقدامی را انتخاب کرد و بافت ورودی کامل چه بود؟
- دستور نرمالسازی شده از ابزار. مجری قطعی (deterministic executor) پس از اعمال قالب (template)، منطق پر کردن دادهها (hydration logic) و قوانین اعتبارسنجی، چه چیزی ساخته است؟
- پیام در صندوق ورودی ایزوله شده. نه لاگی از آنچه فکر میکنید ارسال کردهاید، بلکه پیام واقعی MIME، شامل تمام هدرها، که در یک صندوق ورودی تست اختصاصی ثبت شده است.
- اثر نهایی پس از کلیک بر روی لینک. وضعیت صفحه حاصل، تغییر در پایگاه داده، یا رویداد خارجی که ثابت میکند ایمیل به هدف خود رسیده است.
اگر حتی یک مورد از اینها مفقود باشد، تیم شما شکاف موجود را با فرضیات پر خواهد کرد. آنها حدس خواهند زد. حدس زدن در اتوماسیون هزینهبر است. باعث هدر رفتن ساعتها زمان، از بین رفتن اعتماد و تبدیل شدن هر حادثه به یک معمای جرمشناسی (forensic mystery) به جای یک
