Email simulator
ReplyLayer's first-party simulator lets you exercise send outcomes, signed webhook handling, inbound ingestion, scanning, and quarantine without contacting a real recipient. Outbound tests use reserved recipient addresses. Inbound tests use a dedicated API operation that creates a synthetic message in one of your mailboxes.
Client availability (2026-07-14). The outbound addresses and REST operations on this page are available on the production API. The
rly simulate inboundcommand in CLI source0.7.12and therl.simulatorhelpers in TypeScript and Python SDK source0.25.0are release-ready but are not in the current registry releases. Use the REST example below until your installed client includes those helpers. Outbound addresses already work through every client's normal send method.
Outbound scenarios
Send to a simulator address through the normal send, reply, draft-send, scheduled-send,
or approval flow. The synchronous send response is normal: a clean message is accepted
with status: "sent". A few seconds later the selected outcome is applied, like an
asynchronous delivery-provider event.
rly send --from <mailbox> \
--to [email protected] \
--subject "simulator check" \
--body "exercise the delivered path" \
--json| Recipient | Later message state | Webhook event | Event-specific result |
|---|---|---|---|
[email protected] | delivered | message.delivered | to contains the simulator address |
[email protected] | bounced | message.bounced | bounce_type is simulator |
[email protected] | No later state transition | recipient_blocklist.added | reason is complaint |
[email protected] | No later state transition | recipient_blocklist.added | reason is suppressed |
Append +<label> to any scenario local part for correlation, as in
[email protected]. The label does not change the
outcome. It remains in the recipient address carried by the stored message and webhook.
The domain match is exact. Only simulator.replylayer.net selects this behavior.
simulator.replylayer.ai, a subdomain, or a longer lookalike domain is treated as a
normal recipient and may be handed to your configured transport. Do not infer aliases.
Billing, limits, and reputation
A simulator send consumes paid usage and daily send allowance like a normal send.
Sandbox accounts may send to these addresses without first confirming the recipient,
but the send still consumes the Sandbox daily and cumulative allowances. The four exact
scenario addresses, including their +label forms, are exempt from Sandbox
recipient-domain concentration limits because ReplyLayer makes no network delivery.
One Sandbox account can therefore run all four scenarios in the same 24-hour period.
That exemption does not apply to other local parts, subdomains, or lookalike domains.
Authentication, API rate limits, scanning and review policy, idempotency, and normal usage accounting continue to apply. Paid accounts are charged for simulator sends in the same way as normal sends.
Synthetic bounces and complaints are excluded from account reputation and abuse
counters. No outbound simulator scenario writes a real do-not-contact row. In
particular, complained@ and suppressed@ prove webhook delivery, signature
verification, and event parsing only. A later send to the same simulator address is
not rejected by your account's suppression gate. Use the
suppressions guide to test real do-not-contact behavior.
Test webhook payloads directly
You can test the supported real-shaped webhook payloads without sending a message:
rly webhook test <webhook-id> --event message.delivered
rly webhook test <webhook-id> --event message.bounced
rly webhook test <webhook-id> --event recipient_blocklist.addedThe event-specific test still bypasses the webhook's enabled_events filter, just
like the default test delivery. Omitting --event preserves the original
webhook.test envelope. See Webhooks API: Send a test event
for the REST request and payload contract.
Inject inbound messages
Inbound simulation sends a synthetic message through the same ingestion and scanning behavior as ordinary inbound mail. The scan result is genuine, not a caller-selected verdict.
| Scenario | Guaranteed behavior | Expected result |
|---|---|---|
clean | A clean synthetic message is scanned | Normally available |
prompt_injection_quarantined | The stored scan contains a real prompt-injection finding | quarantined when structural non-override protection is active; otherwise the real scanner decision is returned |
Always branch on the returned status; do not infer it from the scenario name.
When the prompt-injection scenario returns quarantined, the message is releasable
through the normal quarantine API and CLI. The simulator never fabricates a terminal
state to make the scenario name come true.
CLI
rly simulate inbound --mailbox <mailbox> --scenario clean --label ci-run-42 --json
rly simulate inbound --mailbox <mailbox> --scenario prompt_injection_quarantined --jsonREST API
curl -X POST https://api.replylayer.ai/v1/simulator/inbound \
-H "Authorization: Bearer $REPLYLAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mailbox_id": "support-bot",
"scenario": "clean",
"label": "ci-run-42"
}'TypeScript SDK
const result = await rl.simulator.injectInbound({
mailbox_id: mailbox.id,
scenario: 'clean',
label: 'ci-run-42',
});
console.log(result.status, result.message_id);Python SDK
result = rl.simulator.inject_inbound({
"mailbox_id": mailbox["id"],
"scenario": "clean",
"label": "ci-run-42",
})
print(result["status"], result.get("message_id"))Inbound response and follow-up
status | Meaning |
|---|---|
available | Ingestion and scanning finished; the message is available in the mailbox. |
quarantined | Ingestion and scanning finished; use the returned message_id with the normal quarantine workflow. |
pending | The bounded API wait ended before scanning did. message_id is omitted; the message continues processing. |
Pass a label when a test needs to recover from pending. After sanitization, the
label is appended to the synthetic message subject in square brackets, so
rly inbox list --mailbox <mailbox> can identify the eventual message.
An admin key may target any mailbox in its account. An agent key must be scoped to
the target mailbox. Repeated injections are rate-limited and can return
429 RATE_LIMITED.
Inbound simulation deliberately bypasses per-mailbox sender allowlist and blocklist
evaluation so it always reaches scanning. firewall_blocked is not a simulator
outcome. Test sender-policy behavior separately with the
inbound firewall guide.
Related pages
- Messages API - normal send, read, state, and release operations.
- Webhooks - signatures, retries, delivery history, and event catalog.
- Content scanning - scan findings and quarantine semantics.
- Tiers, quotas, and Sandbox limits - send allowance and Sandbox behavior.