Files
multica/server/migrations/098_contact_sales_inquiries.up.sql
Bohan Jiang 7984606eed feat(landing): add Contact Sales page and inquiry endpoint (MUL-2493) (#2988)
* feat(landing): add Contact Sales page and inquiry endpoint (MUL-2493)

Adds a public `/contact-sales` marketing page with a needs-discovery form
modelled on the design reference attached to MUL-2493 — first/last name,
business email (with free-provider rejection), company name + size,
country/region, intended use case, and a free-text goals field, plus the
two consent checkboxes from the reference.

Submissions hit a new public `POST /api/contact-sales` endpoint with
per-IP rate limiting (Redis-backed via the existing RateLimit middleware,
configurable through `RATE_LIMIT_CONTACT_SALES`) and a per-email hourly
cap so a single business address can't be used as a flood channel after
one valid pass. The inquiry is stored in a new `contact_sales_inquiry`
table; analytics fires a `contact_sales_submitted` PostHog event with
only the closed-enum dimensions (size, country, use case) — the free-text
goals stay in the DB and are never broadcast.

The page is linked from the landing header (md+) and the footer's Company
column, in both English and Simplified Chinese. The reserved-slug list is
updated so a workspace named `contact-sales` can't shadow the route.

Co-authored-by: multica-agent <github@multica.ai>

* fix(landing): canonicalize business email and tighten contact-sales form (MUL-2493)

- Parse the submitted email with net/mail and run the free-email
  block-list against the canonical addr.Address, so a display-name
  form like `Ada <ada@gmail.com>` can no longer slip past the gate
  (the raw string had domain `gmail.com>`, which wasn't blocked).
  Adds regression tests covering the display-name bypass and the
  canonicalization helper.
- Drop noValidate from the contact-sales form so the browser's
  native required / email / select checks fire before submit;
  the JS-side free-email warning still runs as a UX guard.
- Update success copy ("respond within three business days") in
  EN and ZH plus the page metadata.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-22 13:22:36 +08:00

27 lines
1.2 KiB
SQL

-- Contact-sales inquiries submitted from the public marketing site.
--
-- The endpoint is unauthenticated so the row has no user_id / workspace_id.
-- Spam mitigation is handled by per-IP rate limiting + business-email
-- validation at the handler layer. We store the IP only so the abuse signal
-- survives a process restart; we never expose it back through the API.
CREATE TABLE contact_sales_inquiry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
business_email TEXT NOT NULL,
company_name TEXT NOT NULL,
company_size TEXT NOT NULL,
country_region TEXT NOT NULL,
use_case TEXT NOT NULL,
goals TEXT NOT NULL DEFAULT '',
consent_outreach BOOLEAN NOT NULL DEFAULT false,
consent_updates BOOLEAN NOT NULL DEFAULT false,
submitter_ip INET,
user_agent TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_contact_sales_inquiry_created ON contact_sales_inquiry(created_at DESC);
CREATE INDEX idx_contact_sales_inquiry_email_created ON contact_sales_inquiry(business_email, created_at DESC);