Skip to content

Signup guards for hosted: block disposable/free emails, cap signups per IP per day, optional real-person check #71

Closed

Description

@clbotdev

Background

app.sendlit.app has open self-serve signup. A shared-infrastructure email sender with open signup attracts spammers, and one bad account can damage everyone's deliverability and our ESP/payment standing. Source: Jesse Hanley's anti-abuse checklist (shared by Marc Lou on X, 18 Oct 2026).

Current state on main (7ce6795):

  • Email verification is already in place. Login and signup use Better Auth emailOTP (apps/api/src/auth/better-auth.ts:202-213), so an account can't exist without proving it owns the mailbox. databaseHooks.user.create.after creates the default org (better-auth.ts:173-178).
  • OTP rate limit is per minute only. It's rateLimit: { window: 60, max: 3 } (better-auth.ts:209-212). There's no daily cap on new-account creation per IP.
  • No disposable/temporary email or free-provider blocking. Searching apps/api/src for "disposable", "blocklist" or "temp mail" finds nothing.
  • No real-person check at signup.

Part of #75 (hosted abuse prevention umbrella). This blocks inviting public users.

Proposed fix

  1. Block disposable/temp email domains at signup, using a maintained list (e.g. the disposable-email-domains dataset, refreshed regularly) in a Better Auth before hook on new-user creation. Optionally add MX checks.
  2. Free providers (gmail.com, outlook.com, etc.): make blocking or flagging configurable. Default to flag for review and lower initial quota rather than hard-block, because many creators use Gmail. Make it hard-block on the Free plan if we choose.
  3. Rate-limit new-account creation to about 3 signups per IP per day (Redis-backed, trusting x-forwarded-for only behind our proxy). Keep the existing per-minute OTP limit.
  4. Optional real-person check:
    • On signup, enrich the email/domain via a people/company lookup (e.g. Exa AI).
    • Store a risk score on the org.
    • Low-confidence accounts start with sending disabled or a low cap until an admin approves.
    • Feature-flagged, off for self-host.

Acceptance criteria

  • Signups from known disposable domains are rejected with a clear message (tested)
  • Free-provider policy is configurable (allow / flag / block) and flagged accounts are visible to admins
  • A 4th new-account signup from one IP within 24h is rejected (tested; respects trusted proxy config)
  • Optional real-person/risk check runs on signup behind a flag and stores a score; high-risk orgs start sending-restricted
  • All guards are hosted-only and off by default for self-host
  • Verified-email requirement documented as already met (emailOTP)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestseverity: highMajor gap or broken flow for many users

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions