Catalogue & product data
Contact Form Builder
Generated from docs/plan/core/contact-form-builder/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Core form-collection feature enabling merchants to receive customer inquiries via a simple, embeddable contact form (name, email, subject, message, optional phone). Stores submissions in an inbox, sends confirmation emails to customers, and notifies merchants. This is foundational infrastructure for every storefront; support-ticket routing belongs in a paid ticketing system.
The problem
Merchants have no way to receive customer inquiries; potential sales emails go to /dev/null because there’s no contact form.
What it does
- Contact form: name, email, subject (optional), message (required, max 5000 chars), phone (optional), form submission via POST
- CAPTCHA protection: reCAPTCHA v3 or hCaptcha, configurable by merchant
- Confirmation email: sent to customer immediately after submission (‘We received your message’)
- Merchant notification: email sent to merchant’s contact address with customer message and reply-to address
- Form submission storage: contact_submission (id, name, email, subject, message, phone, submitted_at, ip_address, status: ‘new’ | ‘read’ | ‘archived’)
- Admin inbox: list all submissions, mark as read/archived, search by email or subject, filter by date range
- Reply inbox: merchant can reply directly to a submission via admin panel; reply is sent as email
- Custom form fields: merchant can add optional fields (e.g., company name, order number) to the form builder UI
- Form spam filtering: reject submissions with obvious spam patterns (all-caps, excessive links, known spam keywords)
- Rate limiting: max 5 submissions per IP per hour (configurable per merchant)
- Webhook on new submission: plugin can listen to ‘contact.submitted’ event
- Export submissions: CSV or JSON export of all submissions by date range
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Support ticket routing and assignment — merchant replies manually; ticketing is a paid tier plugin
- SLA enforcement or escalation — that’s support-team infrastructure, out of scope
- Knowledge base integration — FAQs and docs are separate; contact form is capture only
- Sentiment analysis or auto-routing — categorization is merchant-manual only; ML belongs in paid tier
- Integration with external helpdesk (Zendesk, Intercom) — manual export/import; automated sync is a paid integration
Data model
New entity: contact_submission (id, name: string, email: string, subject: string (nullable), message: string, phone: string (nullable), custom_fields: JSON (key-value pairs for merchant-defined fields), submitted_at: timestamp, ip_address: string, user_agent: string, status: ‘new’ | ‘read’ | ‘archived’, merchant_reply: string (nullable), merchant_reply_sent_at: timestamp (nullable), merchant_reply_from: user_id (nullable)).
API
- POST /contact/submit — accept contact form submission {name, email, subject?, message, phone?, custom_fields?}, return {success: boolean, submission_id: string}
- GET /contact/submissions — list all submissions (admin only), with pagination and filters (date, email, status)
- GET /contact/submissions/:id — retrieve a single submission (admin only)
- PUT /contact/submissions/:id/status — update status to ‘read’ or ‘archived’ (admin only)
- POST /contact/submissions/:id/reply — send a reply email (admin only), store reply in submission
- GET /contact/submissions/export — export submissions as CSV or JSON (admin only)
- PUT /contact/settings — configure CAPTCHA, notification email, form fields, rate limit, spam filters
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Contact management panel includes: (1) Inbox view (list all submissions, mark read/archived), (2) Submission detail page (show message, merchant reply form), (3) Form builder (add custom fields, reorder fields), (4) Settings (CAPTCHA keys, notification email, rate limit, spam keywords), (5) Export button (CSV/JSON), (6) Stats (submissions per day, avg response time if replies tracked).
The seam — why this is core
Core owns the contact form, storage, notification email, and reply mechanism. This is foundational infrastructure. Support-ticket assignment, SLA enforcement, and knowledge base routing are owned by a paid support plugin. A merchant can use the core contact form without any paid tier.
Core owns the interface + honest form collector; contact forms are basic infrastructure, not a support commitment or credential.
Dependencies
- email-layer (core; to send confirmation and merchant notification emails)
- captcha-integration (core; to protect form from spam)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- A POST /contact/submit with name=‘Alice’, email=‘alice@example.com’, message=‘How long is shipping?’ stores the submission and sends a confirmation email to alice@example.com.
- A merchant notification email is sent to the configured contact email (e.g., support@shop.com) with Alice’s message and a reply-to address.
- The submission appears in GET /contact/submissions with status=‘new’; merchant clicks ‘Mark as read’ and status changes to ‘read’.
- Merchant replies via admin panel (POST /contact/submissions/:id/reply with text); reply is sent to alice@example.com with a unique reference.
- Rate limiting rejects a sixth submission from the same IP within an hour, returning a 429 error.
- A submission with message=‘BUY VIAGRA NOW!!!!!!!’ is rejected by spam filter and logged, not stored.
- Exporting submissions as CSV includes name, email, subject, message, submitted_at, status columns.
- Custom field ‘Order Number’ added by merchant appears in the form and is stored in custom_fields JSON.
Risks
If CAPTCHA is misconfigured, bots flood the inbox. If the merchant notification email is undeliverable, they never see submissions. If replies are not tracked or monitored, customers think their messages were ignored. A spam filter that is too aggressive rejects legitimate inquiries; one that is too lenient clogs the inbox.
Commercial context
| Suggested price | free (core) |
| Rival anchor | Shopify: Contact Form (included); Magento: included |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.