Send templated email to top-N customers
hitl_approval"proposed_actions approved with action_type=bulk_customer_email"| name | required | type / hint |
|---|---|---|
customer_cohort_filter | required | — |
subject | required | — |
body_template | required | — |
attachments[] | optional | — |
max_recipients | optional | — |
recipientscustomersSELECT id, companyname, email FROM customers WHERE __filter__ AND isinactive != 'T' AND email IS NOT NULL
prior_sendsoutbound_email_logSELECT customer_id, MAX(sent_at) FROM outbound_email_log WHERE related_workflow='batch_notify' GROUP BY customer_id
| check | rule | severity |
|---|---|---|
recipients_cap | recipients.length <= COALESCE(max_recipients, 50) | block |
no_recent_spam | recipients have NOT been emailed in last 48 hours | warn |
kill_switch | NOT kill:high_risk_ops | block |
Risk level 4 ≥ 3 — runner stages a proposed_actions row before fan-out runs. Mike must approve in proposed-actions.html before any side-effect step executes (real or stub).
workflow_batch_notify (proposal envelope)workflow:batch_notify:run_<run_id>per_recipient_personalize loop_over_recipients STUBloop_over_recipientsstage proposed_action per customer with personalized bodysrc/lib/workflow_runner.ts (kind loop_over_recipients hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.hitl_per_recipient hitl_review STUBMike reviews per-recipient (or accepts cohort-wide approval)src/lib/workflow_runner.ts (kind hitl_review hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.send send_email STUBworkflow_id+customer_idabort_after_5_consecutive_failuressrc/lib/workflow_runner.ts (kind send_email hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.| id | action | source |
|---|---|---|
runner_log_run | INSERT into workflow_run_log (run_id, workflow_type, status, started_at, completed_at, summary_json) | runner automatic |
runner_reflexion | INSERT into reflexion_log (tags=batch_notify, run_id, narrative) | runner automatic (reflexion_enabled=1) |
log_run | INSERT workflow_runs | declared in contract |
reflexion | INSERT reflexion_log with tags=batch_notify | declared in contract |
workflow_verify_results (pending — verify cron not yet wired)all_recipients_loggedSELECT COUNT(DISTINCT customer_id) FROM outbound_email_log WHERE workflow_run_id=?
3exponential200030000truetrue| system | table / resource | action | status | source |
|---|---|---|---|---|
| D1 | workflow_run_log | INSERT (run summary) | REAL | runner automatic |
| D1 | reflexion_log | INSERT (tags=batch_notify) | REAL | runner automatic |
| Event | workflow.completed (or workflow.failed) | fire | REAL | runner automatic |
| D1 | workflow_verify_results | INSERT pending × 1 | REAL | runner verify staging |
| D1 | proposed_actions | INSERT (HITL gate envelope) | REAL | runner HITL gate |
| Loop | recipients | iterate inner steps | STUB | fan-out #1 (per_recipient_personalize) |
| D1 | proposed_actions | INSERT (hitl_review) | STUB | fan-out #2 (hitl_per_recipient) |
| Email Routing | recipient | send | STUB | fan-out #3 (send) |