12 — Forms and Data Collection
PRD Document · Savoy Signature Hotels — Multi-Site Headless Platform
Version: 1.0 · Date: 2026-03-04
Related docs:06_Content_Modeling_Umbraco.md,08_API_Contracts.md
1. Purpose
Section titled “1. Purpose”This document outlines the architecture for dynamic forms and data collection across the Savoy Signature platform. It defines how forms are created in the CMS, rendered in the headless frontend, validated, submitted safely, and how notifications/integrations are handled.
2. Form Architecture Overview
Section titled “2. Form Architecture Overview”We use Umbraco Forms combined with a custom GraphQL/REST endpoint that exposes the form definition to Next.js.
2.1 Workflow
Section titled “2.1 Workflow”3. Supported Form Types
Section titled “3. Supported Form Types”| Form Name | Common Placement | Primary Action | Data Destination |
|---|---|---|---|
| Contact Us | Generic contactPage | Email to specific hotel department | Umbraco DB + Email |
| Newsletter Signup | Footer (Global) | Subscribe to list | Umbraco DB + CRM |
| Event Enquiry | eventsPage | RFP to Sales team | Umbraco DB + Email |
| Wedding Enquiry | celebrationsPage | Detailed RFP to Events team | Umbraco DB + Email |
| Spontaneous Application | careersPage | Upload CV | Umbraco DB + Email |
4. Frontend Rendering
Section titled “4. Frontend Rendering”The frontend component receives a JSON schema defining the form and renders it using react-hook-form + zod for validation.
4.1 Form Schema Example
Section titled “4.1 Form Schema Example”{ "formId": "contact-savoy-palace", "name": "Contact Us", "submitLabel": "Send Message", "successMessage": "Thank you. Your message has been sent.", "fields": [ { "alias": "firstName", "label": "First Name", "type": "text", "required": true, "cssClass": "col-span-12 md:col-span-6", "validation": { "maxLength": 50 } }, { "alias": "email", "label": "Email Address", "type": "email", "required": true, "cssClass": "col-span-12 md:col-span-6", "validation": { "pattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" } }, { "alias": "department", "label": "Department", "type": "dropdown", "required": false, "options": [ { "value": "reservations", "text": "Reservations" }, { "value": "concierge", "text": "Concierge" }, { "value": "events", "text": "Events & Groups" } ] }, { "alias": "message", "label": "Your Message", "type": "textarea", "required": true, "cssClass": "col-span-12", "validation": { "minLength": 10 } }, { "alias": "consent", "label": "I agree to the Privacy Policy", "type": "checkbox", "required": true } ]}4.2 React Implementation (Conceptual)
Section titled “4.2 React Implementation (Conceptual)”'use client';
import { useForm } from 'react-hook-form';import { zodResolver } from '@hookform/resolvers/zod';import { generateZodSchema } from './form-schema-generator';
export function FormModule({ schema, siteKey, locale }) { const [isSubmitting, setIsSubmitting] = useState(false); const [status, setStatus] = useState<'idle'|'success'|'error'>('idle');
// Dynamically generate validation based on CMS rules const zodSchema = generateZodSchema(schema.fields); const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(zodSchema) });
const onSubmit = async (data) => { setIsSubmitting(true);
// Inject anti-spam token (reCAPTCHA v3 or Turnstile) const token = await window.grecaptcha.execute();
const res = await fetch(`/api/forms/${schema.formId}/submit`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...data, token, siteKey, locale }), });
setStatus(res.ok ? 'success' : 'error'); setIsSubmitting(false); };
if (status === 'success') { return <div className="form-success">{schema.successMessage}</div>; }
return ( <form onSubmit={handleSubmit(onSubmit)} className="dynamic-form"> {/* Dynamic rendering of fields based on schema.fields[] */} </form> );}5. Security and Spam Prevention
Section titled “5. Security and Spam Prevention”Form endpoints are highly susceptible to abuse. The platform employs a multi-layered defense.
5.1 Defense Mechanisms
Section titled “5.1 Defense Mechanisms”| Layer | Implementation | Purpose |
|---|---|---|
| 1. Honeypot Field | Hidden <div> with an input field named website | Bots fill hidden fields; humans don’t. Reject if filled. |
| 2. Rate Limiting | Next.js API Route + Cloudflare WAF | Max 5 submissions per IP per 5 minutes. |
| 3. reCAPTCHA v3 | Invisible token generation on onSubmit | Score-based validation (e.g., block if score < 0.5). Requires no friction for real users. |
| 4. Server Validation | Umbraco API endpoint | Ensure payload matches field definitions (no injected fields, correct lengths). |
| 5. CSRF Protection | Next.js + CORS | Ensure request originates from Savoy domains. |
6. Email and Notifications
Section titled “6. Email and Notifications”Umbraco Workflows trigger emails via the external SMTP service defined in ADR-005.
6.1 Email Dispatch Rules
Section titled “6.1 Email Dispatch Rules”- Provider: Mailjet or SendGrid (configured in Azure App Settings).
- Sender Address:
[email protected](authenticated via SPF, DKIM, DMARC on Cloudflare DNS). - Reply-To: The email address submitted in the form.
- Routing: Contact forms route to different inboxes based on the selected “Department” field or the
siteRootconfiguration.
6.2 File Upload Handling
Section titled “6.2 File Upload Handling”For forms accepting files (e.g., CV uploads on Careers):
- User uploads file (max 5MB, PDF/DOCX only).
- Frontend converts file to Base64 or FormData.
- API endpoint receives file, scans (if applicable), and saves to a secure Azure Blob Storage container (
/form-uploads). - The email notification contains a secure link to download the file (requires CMS authentication or expiring SAS token).
- DO NOT attach large files directly to emails.
7. Compliance and GDPR
Section titled “7. Compliance and GDPR”| Requirement | Implementation |
|---|---|
| Consent | Absolute requirement for a mandatory checkbox linking to the Privacy Policy. |
| Data Retention | Umbraco Forms configured to auto-delete submissions older than 90 days from the database. |
| Right to Erasure | User requests handled by DPO via Umbraco backoffice. |
| Export | CMS editors can export form data to Excel/CSV for reporting. |
8. Acceptance Criteria
Section titled “8. Acceptance Criteria”- Form schemas fetched dynamically from Umbraco API.
- Unknown field types gracefully ignored by frontend renderer.
- Client-side validation blocks submission of invalid emails or required fields.
- Honeypot field successfully blocks automated bot submissions silently (returns fake 200).
- reCAPTCHA v3 token validated server-side.
- Email sent securely via centralized SMTP without exposing sender reputation issues.
- File uploads do not attach to emails, but store securely on Azure Blob.
- GDPR mandatory consent checkbox configured on all forms.
- Rate limits actively drop excessive API calls.
Next document: 13_Media_and_Image_Pipeline.md