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.
3. Інструмент перевіряє дозволи та обов'язкові поля.
Чи має цей контекст агента право викликати send_review_request для цього користувача? Чи є область отримувачів не порожньою та чи в межах дозволених лімітів? Чи присутній ключ ідемпотентності та чи є він унікальним у вашому журналі? Чи є trace ID коректно сформованим? Якщо ні — видавайте помилку тут, гучно, ще до того, як буде задіяно будь-яку службу електронної пошти.
4. Служба електронної пошти реєструє відправлення з trace ID.
Кожне повідомлення, що покидає вашу систему, має нести цей ідентифікатор трасування через API провайдера у ваш стек спостережуваності (observability stack). Якщо користувач скаржиться на отримання двох копій, ви повинні мати можливість запитати за одним ID і побачити, де саме виникло дублювання: повторний виклик агента, нестабільний (flaky) виконавець або некоректний callback.
5. E2E-тест перевіряє вміст та ефект у реальному поштовому ящику.
Відкрийте відрендерене повідомлення в справжньому поштовому ящику. Чи правильно заповнено тему листа? Чи працює посилання для відписки? Чи веде натискання на основну кнопку заклику до дії (call-to-action) на правильну сторінку з відповідним станом користувача? Успішний юніт-тест означає, що код спрацював. Тільки тест у поштовому ящику скаже вам, чи справді лист працює для людини.
Докази замість здогадок
Коли тест у цьому конвеєрі (pipeline) завершується невдачею, вам потрібні чотири конкретні докази. Не погоджуйтеся на менше.
- Початкове рішення агента. Яку дію він обрав і яким був повний контекст вхідних даних?
- Нормалізована команда від інструмента. Що створив детермінований виконавець після застосування шаблону, логіки гідрації (hydration logic) та правил валідації?
- Повідомлення в ізольованому поштовому ящику. Не лог того, що, на вашу думку, ви відправили, а справжнє MIME-повідомлення з усіма заголовками, зафіксоване в спеціальному тестовому поштовому ящику.
- Кінцевий ефект після натискання на посилання. Стан сторінки, що виник у результаті, зміна в базі даних або зовнішня подія, яка доводить, що електронний лист досяг своєї мети.
Якщо хоча б одного елемента бракуватиме, ваша команда заповнить прогалини припущеннями. Вони будуть гадати. Здогадки в автоматизації коштують дорого. Вони витрачають години, підривають довіру та перетворюють кожен інцидент на криміналістичну загадку замість...
