gigafibre-fsm/apps/ops
louispaulb 6577bb79bc feat(campaigns/send): real SMTP error + auto-retry + one-click Renvoyer
The send worker used to write "SMTP send returned false (see hub logs)"
on every failure, forcing the operator to SSH into the box to find the
actual cause. Now we capture the real reason and surface it in the UI.

Three changes:

1. lib/email.js exposes getLastError() — a side-channel for the most
   recent nodemailer error message, cleared at the start of every
   sendEmail call. Legacy "if (await sendEmail(...))" callers stay on
   the false-return contract; only the campaign worker reads the
   side-channel for detailed error capture.

2. The worker now retries each recipient up to 3 times (initial +
   2 retries with 2s/5s backoff). Most "Unexpected socket close"-style
   transient Mailjet errors recover on the second attempt. We observed
   exactly this case for Myriam Bergevin in cmp-20260522-2d4605 — a
   single socket close interrupted 1 of 202 sends; auto-retry would
   have caught it. retry_count is now stored on the recipient.

3. POST /campaigns/:id/recipients/:row/retry resets a single failed
   row back to pending and re-fires the worker. Surfaced in the
   detail-page table as a small 🔁 button next to the error text on
   any row with status=failed. Useful when auto-retry exhausted its
   3 attempts on a one-off transient.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 13:29:25 -04:00
..
.quasar feat(ops/client): edit/delete/reorder subscriptions + rebate nesting 2026-04-23 11:21:41 -04:00
infra docs: reorganize into architecture/features/reference/archive folders 2026-04-22 11:51:33 -04:00
public/icons feat: add ops app + CONTEXT.md, simplify URL to /ops/ 2026-03-30 22:41:58 -04:00
src feat(campaigns/send): real SMTP error + auto-retry + one-click Renvoyer 2026-05-22 13:29:25 -04:00
src-pwa feat: nested tasks, project wizard, n8n webhooks, inline task editing 2026-04-01 13:01:20 -04:00
.env.example feat(hub+ops): user invite flow sends temp password via Mailjet + dev .env.example 2026-05-05 19:50:06 -04:00
deploy.sh feat: nested tasks, project wizard, n8n webhooks, inline task editing 2026-04-01 13:01:20 -04:00
index.html feat: add ops app + CONTEXT.md, simplify URL to /ops/ 2026-03-30 22:41:58 -04:00
package-lock.json feat(ops/campaigns): pivot template editor to Unlayer (vue-email-editor) 2026-05-22 06:14:06 -04:00
package.json feat(ops/campaigns): pivot template editor to Unlayer (vue-email-editor) 2026-05-22 06:14:06 -04:00
quasar.config.js feat: contract → chain → subscription → prorated invoice lifecycle + tech group claim 2026-04-22 20:40:54 -04:00