Skip to content

fix(sending): explain domain verification and fix the feedback test status - #191

Merged
s3rot0n1n merged 2 commits into
mainfrom
fix/sending-requirements-followup
Nov 12, 2026
Merged

s3rot0n1n merged 2 commits into
mainfrom
fix/sending-requirements-followup

Conversation

@s3rot0n1n

Copy link
Copy Markdown
Contributor

Follow-up to #190, from setting up SES feedback for a team in production.

Problems

  • The domain-verification block wasn't explained. After the cloud test volume, SendLit also requires a verified From domain (domain_verification_required). In a sequence this failed before the send, so after five attempts the sequence paused with "several emails in a row failed to send", which doesn't tell the owners what to fix.
  • The warnings only mentioned feedback. The 50/90/100% emails and the ESP notice talked about bounce/complaint feedback but not domain verification, so owners fixed one block and then ran into the next.
  • The feedback dialog showed a stale status after Test. A passing Test made the connection healthy, but the dialog kept showing "Waiting for first event" until it was reopened. The Test was also recorded as "Last event", which looked like a real bounce had arrived.

Changes

  • Domain-verification pause: domain_verification_required is now a stop the team has to fix. The sequence auto-pauses with the sending_blocked reason, records the domain, and emails the owners once ("its From domain mail.example.com isn't verified… Verify the domain in Organizations → Sending domains"). The dashboard banner shows the same message.
  • Warnings: the milestone emails list exactly what's left: ESPs without feedback, and From domains the organization hasn't verified. They're only sent when something is left. The ESP notice mentions both requirements.
  • Feedback dialog: after a passing Test, the dialog reloads the connection. A manual Test no longer sets lastReceivedAt; only real webhook events do. The dialog shows "Tested: …" until a real event arrives.
  • Telemetry: adds domain_count to the reminder event and the PostHog allowlist.

There are no API contract changes, so openapi.json is unchanged.

Testing

  • New tests: an unverified-domain pause names the domain and emails once without using up retries; a domain-only warning lists the domain and not feedback; no warning is sent when feedback is connected and the domain is verified; a manual Test makes the connection healthy without setting lastReceivedAt, and a real event does set it.
  • Suites: sending-health, feedback-requirement, webhook-and-connection, process-receipt, feedback-worker and the MCP delivery-feedback tests pass. Web tests pass (116), and the API and web typecheck cleanly.
  • Locally failing: 3 process-ongoing-sequence tracking-link tests fail on my machine because of local tracking-host settings (HTTP instead of HTTPS). They fail on main locally too and pass in CI.

🤖 Generated with Claude Code

…tatus

Follow-up to #190, from setting up SES feedback in production.

- An unverified From domain (domain_verification_required) now
  auto-pauses a sequence with the sending_blocked reason, names the
  domain, and emails the owners once, instead of failing five times and
  pausing as "several emails in a row failed to send".
- The 50/90/100% warnings and the ESP notice cover domain verification
  as well as feedback, listing exactly what is left to do.
- The feedback dialog reloads the connection after a passing Test, so it
  shows Healthy. A manual Test no longer sets "Last event"; it shows when
  it was tested instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T04:35:10.582461Z 0be1651 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0be1651ff8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +103 to +105
for (const esp of esps) {
const domain = fromDomain(esp.fromEmail);
if (domain && !verifiedDomains.has(domain)) domains.add(domain);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include organization-owned ESPs in domain reminders

When an organization sends only through a shared organization-owned ESP, its config has no teamId, so it is absent from esps and this loop never checks its From domain. notifyFeedbackMilestones then sees no unmet requirements and skips the 50/90/100% warning, even though assertSendingEligibility checks that ESP’s domain and will block sending at the threshold. Include organization-owned ESPs when building the domain reminders.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8b1baea. The reminders now also check the From domains of the organization's active organization-owned ESPs, so an organization sending only through a shared ESP gets the 50/90/100% warnings for an unverified domain. A new test covers it. Shared ESPs stay off the feedback list in these reminders, because the feedback gate is checked per sending team; that's tracked separately.

@Robertpaschal

Copy link
Copy Markdown

The Test-setting-lastReceivedAt fix is the sharp one — a manual test masquerading as a real event corrupts exactly the signal the dialog exists to show, and it's the same class of bug as the two blockers sharing one message. Naming the domain in the pause email is the right call; a paused sequence that doesn't name the blocker is a support ticket waiting to happen. One thing I'd check in the retry path: the five-attempt budget before auto-pause — does it distinguish a domain-verification failure from a transient SES failure? An unverified domain will never succeed on retry, so five attempts just burn the budget and delay the pause email that actually tells the owner what to fix. A deterministic failure wants to pause on first sight, with the retry budget kept for the failures that might clear. Is the domain-verification block recorded as its own sending_blocked reason per domain, so re-verifying unpauses only the sequences blocked on that domain and not everything paused for other reasons?

…nders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@s3rot0n1n

Copy link
Copy Markdown
Contributor Author

Thanks, @Robertpaschal.

  • Retry budget: an unverified From domain doesn't use the five-attempt budget. With this PR, domain_verification_required counts as something the team has to fix, like a missing feedback connection. The sequence pauses on the first failure, and the owners get one email naming the domain. The retry budget and backoff only apply to ESP failures that might clear on their own.
  • Unpausing per domain: the pause records the domain in its details (sending_blocked with error: domain_verification_required, domain). Nothing resumes automatically today, for any pause reason; an owner resumes the sequence after fixing the cause. Resuming automatically when the matching domain is verified is possible later, since the domain is stored, but it's out of scope for this PR.

@s3rot0n1n
s3rot0n1n merged commit 909ccec into main Nov 12, 2026
6 of 8 checks passed
@Robertpaschal

Copy link
Copy Markdown

Thanks for the detailed reply. The per-domain pause on first failure, with the domain stored for a later auto-resume, is exactly the kind of plumbing rootmail is built around: per-message delivery state, bounce and complaint handling, and suppression lists for SES senders. It's in invite-only beta (8 seats, bring your own SES or Mailgun): https://rootmail.io/beta — happy for you to try it against Sendlit's SES path if useful, and happy to keep this thread technical either way.

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