Skip to content

Signup forms: block double opt-in without a sender, and show an error when the confirmation can't be sent #102

Closed

Description

@clbotdev

Problem

Found while manually testing signup forms (#27).

A team with no usable sending mailbox can still create and publish a double opt-in signup form. When someone submits it:

  • the confirmation email fails with delivery_source_unavailable
    (createTransactionalEmail in apps/api/src/forms/queries.ts:222);
  • the error is only logged as a warning ("signup confirmation email not sent") and isn't reported to PostHog;
  • the visitor still sees "Check your inbox. We sent you an email…" (apps/api/src/forms/public-routes.ts:20), so the signup is silently lost;
  • nothing in the dashboard tells the owner.

Repro: sign up a new organization (its team has no ESP), create a signup form with double opt-in on, and submit it. No email reaches Mailpit; the API log shows delivery_source_unavailable.

Mailchimp, Kit and similar tools require a verified sender before a form can be published.

Proposed fix

1. Block at setup

  • Creating or updating a double opt-in form, or switching a form to double opt-in, fails with a clear error when the team has no dispatchable delivery source (no active, or draining-but-valid, team ESP or grant with a from address, or the team or org isn't active). Applies to REST, MCP and the dashboard.
  • The dashboard form dialog explains why and links to Settings → Email service providers.
  • The sender can still disappear later (ESP retired or paused, grant revoked, team paused), so the forms list should show a "can't send confirmations" warning on affected forms.

2. On submission

  • If the confirmation email can't be sent, the visitor gets an error ("This form can't accept signups right now. Please try again later.") instead of "Check your inbox": an error page for the hosted/no-JS form and an error JSON response for fetch/embed submissions.
  • Report the failure through captureError (src/observability, which goes to PostHog) with the form ID and team ID, not just a log line.
  • Keep the anti-enumeration guarantee in submitSignup. The new error depends only on the team's sending setup, never on the submitted address, so suppressed, disposable and rate-limited submissions still get the normal response.

Acceptance criteria

  • API/MCP/dashboard reject double opt-in forms for teams with no delivery source; tests cover it
  • Forms list warns when an existing form's team loses its sender
  • Submitting such a form shows an error (HTML + JSON) and calls captureError
  • REST docs, MCP tool docs and email-marketing/signup-forms.mdx updated
  • Browser E2E: new org without ESP → form creation blocked; add ESP → create form → retire ESP → submit shows error

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions