Skip to content

Sequence enrollments never send: footer check rejects wrapped addresses, and render failures retry forever #154

Closed

Description

@s3rot0n1n

Summary

An active drip sequence sends nothing. Every enrollment fails in attemptMailSending with footer_render_failed and is retried every ~30s forever. Two bugs combine to cause this.

Bug 1: footer compliance check rejects long single-line mailing addresses

apps/api/src/mail/render.ts (mailingAddressAppearsInText, footer check near line 309) requires each non-empty line of the team's mailing address to appear verbatim in document.body.textContent.

The managed footer renders long addresses with a line break and indentation inside the address (for example after a comma). The raw text then never contains the unbroken address, so the check throws footer_render_failed.

Reproduction (no DB or send needed):

const address = "<any single-line address long enough for the footer to wrap>";
const html = await renderEmailToHtml({
  email: content,
  blocks: [SENDLIT_FOOTER_BLOCK],
  renderContext: { footer: { mailingAddress: address, unsubscribeUrl } },
});
const text = new JSDOM(html).window.document.body.textContent ?? "";
text.includes(address);                      // false
text.replace(/\s+/g, " ").includes(address); // true

The unsubscribe href check passes in the same case. The existing multi-line test passes only because the input address is already split by \n.

Impact: every recipient of a team's sequence gets the same footer, so nothing sends for that team until the address is changed.

Suggested fix: collapse whitespace on both the text and each address line before comparing, and add a regression test with a long single-line address.

Bug 2: render failures are never counted, so enrollments retry forever

In automation/process-ongoing-sequence.ts, renderCampaignHtml runs in attemptMailSending before the try block. A render error therefore never increments ongoing_sequences.retry_count and never reaches sequenceBounceLimit. The error propagates, the claim is released, and the row stays due.

Evidence: the stuck enrollment has retry_count = 0 and has been failing for hours. Its logs repeat every ~30s with processOngoingSequence failed and sequence worker failed.

Suggested fix: move the render inside the try (or count render failures explicitly) so repeated failures drop or pause the enrollment.

Acceptance criteria

  • A sequence whose team has a long single-line mailing address sends normally.
  • A permanent render failure is counted and stops retrying after sequenceBounceLimit attempts.
  • Regression tests cover both cases.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingseverity: criticalBreaks sending, data loss or security; fix now

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions