MemberJunction / MemberJunction/MJ
Communication Engine: batch-send error semantics — log failures abort/discard sends, EndRun never marks runs Failed
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 308
Description
## What's wrong
Several error paths in the Communication Engine's send pipeline (`packages/Communication/engine/src/Engine.ts` + `BaseEngine.ts`) lose or block work in ways that bite any multi-recipient consumer:
1. **A DB hiccup blocks the send entirely**: `SendSingleMessage` throws when `StartLog` fails (`Failed to start log for message`) — the message is never dispatched because an *audit* write failed.
2. **A successful send gets reported as a throw**: after the provider accepts the message, a failure updating the log row throws — the caller sees an exception for an email that was actually delivered (double-send bait on retry).
3. **Template-process and log failures abort the whole batch**: inside `SendMessages`, these throw out of the loop, stranding the remaining recipients unsent and the Communication Run in `Pending`. (Provider-level send failures DO continue correctly — `Success=false` results are pushed and the loop proceeds — so the batch contract is inconsistent across failure classes.)
4. **`EndRun` unconditionally sets `Status='Complete'`** — the Runs schema has `Status='Failed'` and `ErrorMessage` columns, but no engine code path ever populates them.
5. **An `EndRun` failure throws *after* all sends succeeded**, discarding the entire results array.
## Proposed fix
Convert per-message throw sites (1–3) into `Success=false` (or result-coded, see the MessageResult issue) entries in the results array so the batch always continues and always returns per-row outcomes; always reach `EndRun` (try/finally); populate `Failed`/`ErrorMessage` on the run when any row failed or the run aborted; never let audit-write failures mask a successful dispatch (log the audit failure on the result instead).
Found while evaluating the engine for CDP's voice-invite batch emails — these semantics (one bad row must not strand N-1 recipients; an accepted send must never surface as a failure) were the deciding reason CDP kept a hand-rolled send loop.
## Acceptance criteria
- [ ] A `StartLog` failure does not block dispatch; it is recorded on that message's result
- [ ] A post-send log-update failure never converts an accepted send into a thrown error
- [ ] A template-render failure for one recipient fails only that row; the batch continues and the run is ended
- [ ] Runs are marked `Failed` + `ErrorMessage` when appropriate; no run is left `Pending` by an engine error path
- [ ] `EndRun` failure does not discard the results array
- [ ] Tests cover each failure class in a multi-recipient batch
## Blocked by
None - can start immediately (per-row reporting is richer with MemberJunction/MJ#2818)
Contributor guide
Assessment
This issue has not been assessed yet.