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 inbound command in CLI source 0.7.12 and the rl.simulator helpers in TypeScript and Python SDK source 0.25.0 are 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
RecipientLater message stateWebhook eventEvent-specific result
[email protected]deliveredmessage.deliveredto contains the simulator address
[email protected]bouncedmessage.bouncedbounce_type is simulator
[email protected]No later state transitionrecipient_blocklist.addedreason is complaint
[email protected]No later state transitionrecipient_blocklist.addedreason 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.added

The 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.

ScenarioGuaranteed behaviorExpected result
cleanA clean synthetic message is scannedNormally available
prompt_injection_quarantinedThe stored scan contains a real prompt-injection findingquarantined 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 --json

REST 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

statusMeaning
availableIngestion and scanning finished; the message is available in the mailbox.
quarantinedIngestion and scanning finished; use the returned message_id with the normal quarantine workflow.
pendingThe 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.