SsuperslateDocs

Notifications and marketing endpoints

Operate the notification inbox, preferences, waitlist, contact form, and Slack alerts.

Notifications and marketing endpoints

The repository contains two distinct communication paths. Authenticated product notifications are stored per user and respect preferences. Public marketing submissions are rate-limited boundary endpoints intended to be called by a buyer-owned marketing site.

Product notifications

The supported notification types are:

TypeDefault emailDefault in-appIncluded producer behavior
billing_updateenabledenabledbilling owns its lifecycle email and emits in-app state
security_alertenabledenabledunrecognized-device sign-in and password reset
system_announcementenabledenabledno included producer; a product emits these deliberately

Producers live with the domain that owns the event. Billing lifecycle notifications are emitted from apps/server/src/domains/billing/service.ts. Security alerts are emitted from the Better Auth hooks in apps/server/src/lib/auth.ts: a sign-in whose user agent has never been seen for that user, and a completed password reset. The first session a user ever creates never alerts, so signup is silent. Device recognition is user-agent based, not a device-identity system; treat it as a heuristic.

There is no email-change alert because the account contract has no email-change flow; UpdateProfileRequestSchema covers name, profile image, and phone number only. Adding one means adding the flow first.

There is no push-delivery implementation. Do not expose a push preference or claim push support without adding a provider, permission UX, subscription ownership, revocation, retry, privacy, and browser proof.

All notification routes require sessionAuth and scope reads and writes to the current users.id:

MethodRouteBehavior
GET/notificationspaginated list with type/unread filters
GET/notifications/unread-countcurrent user's unread count
PATCH/notificationsmark up to 100 IDs or all rows as read
GET/notifications/preferenceseffective email and in-app preferences
PUT/notifications/preferencesupdate deduplicated typed preferences
GET/notifications/:notification_idowner-scoped detail
DELETE/notifications/:notification_idowner-scoped deletion

The browser polls unread count, invalidates only notification query keys after mutations, and sends the HttpOnly Better Auth cookie through the shared Hono client. The service must never accept a user ID from notification request input.

Notification dispatch is best effort after the owning transaction commits. It can be lost when the process exits and is not a durable queue or outbox. If delivery is required for a product invariant, follow Background work and design persisted delivery state.

Waitlist endpoint

POST /marketing/waitlist validates JoinWaitlistRequestSchema, stores one row per normalized email, and returns the same non-disclosing success message for an existing address. The optional fields are referral_source, source_page, and campaign.

The database row is durable. A newly inserted lead also triggers a best-effort Slack alert. Slack failure does not roll back the lead and duplicate submissions do not send another alert.

Contact endpoint

POST /marketing/contact validates ContactFormRequestSchema and sends a best-effort Slack alert. The repository does not persist contact messages and there is no email, retry, inbox, or support SLA. Do not connect a production form until Slack is configured and this loss policy matches the product. Add durable storage or a provider-owned ticket ID when a submission must be recoverable.

Both public endpoints share an atomic Postgres limit of five requests per minute for the resolved client IP. The budget is shared across API replicas and depends on the selected trusted proxy profile producing the correct client IP.

Slack behavior

SLACK_BOT_TOKEN is optional. When absent, signup, waitlist, and contact alerts are disabled and the server continues. When present, the bot posts sanitized text to #alerts-users. The channel is a current application constant, not a configurable routing matrix.

Never treat Slack as the source of truth for a durable lead, a contact-support entitlement, or an incident system. Never include secrets, auth links, cookies, payment data, or unrestricted customer payloads in alert messages.

Verification

bun test --cwd apps/server src/domains/marketing
bun test --cwd apps/server src/domains/notifications
RUN_DB_INTEGRATION_TESTS=1 \
  DATABASE_URL=postgresql://app:app@localhost:5432/app?sslmode=disable \
  bun test --cwd apps/server
pnpm test:e2e

Before launch, prove duplicate waitlist behavior, invalid and oversized input, limit exhaustion, Slack disabled/failure behavior, notification preferences, owner isolation, mark-read limits, and the exact retention policy for leads, contact content, and notifications.

On this page