Policy Simulation
Dry-run sends against the policy engine without actually sending email.
Policy simulation lets you test whether a send would be allowed or blocked — without actually sending the email. Use it to pre-check sends, validate batch campaigns, and inspect remaining quotas.
Simulate a single send
POST https://api.molted.email/v1/agent/simulate-sendmolted send simulate --to user@example.com --subject "Hello" --body "Hi Alice!"molted send simulate \
--to user@example.com \
--template campaign-march \
--dedupe-key "march-user@example.com" \
--payload '{"name": "Alice"}'curl -X POST https://api.molted.email/v1/agent/simulate-send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tenantId": "tenant_abc123",
"recipientEmail": "user@example.com",
"templateId": "campaign-march",
"dedupeKey": "march-user@example.com",
"payload": { "name": "Alice" }
}'| Field | Required | Description |
|---|---|---|
tenantId | yes | Tenant identifier (injected automatically by CLI) |
recipientEmail | yes | Recipient email address |
templateId | yes | Template to validate against |
dedupeKey | no | Deduplication key (checks duplicate policy) |
mailboxId | no | Specific mailbox to resolve sender from |
payload | no | Template variables -- passed to the policy engine for validation |
Response
{
"wouldAllow": true,
"simulation": true
}{
"wouldAllow": false,
"reason": "cooldown",
"cooldownExpiresAt": "2026-03-01T12:10:00Z",
"simulation": true
}{
"wouldAllow": false,
"reason": "suppressed",
"suppressionInfo": {
"scope": "tenant",
"reasonCode": "hard_bounce"
},
"simulation": true
}{
"wouldAllow": false,
"reason": "trial_not_activated",
"simulation": true,
"policyContext": {
"isBillingBlocked": true,
"billingPlan": "trial"
}
}The simulation now checks billing status in addition to policy rules. If your account is on a trial or expired plan, the simulation correctly reports that real sends would be blocked. This prevents false positives where policy checks pass but actual sends fail due to billing.
The response uses the same reason values as real sends — see Errors & Policy Blocks for the full list.
Simulate a batch
Dry-run policy evaluation for up to 500 recipients.
POST https://api.molted.email/v1/agent/simulate-batchcurl -X POST https://api.molted.email/v1/agent/simulate-batch \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tenantId": "tenant_abc123",
"requests": [
{ "recipientEmail": "user1@example.com", "templateId": "campaign-march", "dedupeKey": "march-user1" },
{ "recipientEmail": "user2@example.com", "templateId": "campaign-march", "dedupeKey": "march-user2" }
]
}'Check remaining quotas
GET https://api.molted.email/v1/agent/budgetcurl "https://api.molted.email/v1/agent/budget?tenantId=tenant_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"tenantId": "tenant_abc123",
"monthly": { "used": 450, "limit": 1000, "remaining": 550 },
"daily": { "used": 23, "limit": 10000, "remaining": 9977 },
"hourly": { "used": 5, "limit": 1000, "remaining": 995 },
"negativeSignals": { "count": 2, "budget": 50, "remaining": 48 },
"timestamp": "2026-04-12T12:00:00.000Z"
}Important notes
- No side effects — simulations do not increment counters, create send records, or trigger delivery. They are read-only.
- Billing included — simulations check your billing status and will return
trial_not_activatedorsubscription_expiredif sends would be blocked. ThepolicyContextincludesisBillingBlockedandbillingPlanfields. - Time-of-check vs. time-of-use — a simulation result reflects the policy state at the moment of the check. By the time you submit the actual send, conditions may have changed (e.g., another agent may have consumed budget). Treat simulation results as guidance, not a guarantee.