Skip to content

fix(outreach): keep failed send occurrences out of due batches - #59

Merged
debpalash merged 1 commit into
debpalash:mainfrom
rudycelekli:fix/outreach-terminal-send-candidates-20261006
Oct 7, 2026
Merged

debpalash merged 1 commit into
debpalash:mainfrom
rudycelekli:fix/outreach-terminal-send-candidates-20261006

Conversation

@rudycelekli

Copy link
Copy Markdown
Contributor

What & why

A transient failed send persists an at-most-once send ledger row but leaves the enrollment pending. The bounded due-enrollment query then repeatedly returns that blocked occurrence first; enqueue refuses its existing send record, so later valid leads can never reach the ticker when the candidate limit is filled by old failures.

Mark a newly failed sequence enrollment terminal with its error. Exclude already-recorded current-step occurrences before the due candidate limit, allowing existing deployments' pending enrollments with failed send rows to stop blocking later work. Correlation includes workspace, sequence, enrollment and current step, so a sent previous step does not hide the next due step. Existing at-most-once retry semantics remain intact: a failed send retains terminal send state, and queue replay makes no extra SMTP handoff.

Type

  • Bug fix

Verification

  • Actual persisted sequence/enrollments → native handle_send with failed SendResult → failed send ledger → native replay → later enrollment → real tick_sequence(limit1): 2 failures before, with one passing successful-next-step control.
  • New failures and historical pending-with-failed-ledger state covered. One handoff only on replay; actual pending queue job belongs to the later valid enrollment. Successful previous send still queues its next step.
  • Existing outreach send, unit and inbound modules: 47 passed (including existing no-charge and replay cases).
  • Exact signed full frozen backend selection: 1,881 passed, 131 skipped, 3 deselected, 2 unchanged baseline warnings (uv run --frozen pytest -q -m 'not live and not postgres'). Pristine baseline: 1,878 passed.
  • Diff check passes. Ruff F has only the existing unused test import, no new finding. Owned migrated SQLite environment; no SMTP contacted and no PostgreSQL service/frontend run locally. Hosted CI is pending.
  • Fresh upstream/open changed files screened. fix(scheduler): query persisted SQLite deadlines without timezone errors #49 changes schedule selection and fix(outreach): decode HTML entities in plaintext messages #55 changes plaintext conversion in sending.py; fix(outreach): preserve literal template variable values #51/fix(outreach): decode HTML entities in plaintext messages #55 add separate tests in this module. This change touches separate production regions, with shared-file integration disclosed.

Checklist

  • Commits are signed and signed off per DCO
  • Backend tests pass in the frozen environment described
  • Monorepo lint and build pass (hosted CI pending)
  • I did not add scraping of paywalled / pirated / login-gated content
  • Credentialed providers are opt-in and inert by default

Security-sensitive?

Ordinary terminal enrollment state and queue candidate selection only. No transport, sender retry, charge, auth, secrets, tenancy policy, suppression or URL guard changes.

AI assistance was used for implementation and native tests. The exact signed source packet received independent review before publication.

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
@rudycelekli
rudycelekli requested a review from debpalash as a code owner October 6, 2026 15:53
@debpalash
debpalash merged commit 826bf28 into debpalash:main Oct 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants