Contact Form Setup

This article covers the full configuration path for the /api/contact endpoint — from registering a Turnstile widget to verifying that submissions land in your portal. If you want an overview first, read Website Integration.


How /api/contact works

Contact form data flow — visitor submits, Turnstile verification, Worker, D1 database and Resend notification

When a visitor submits the contact form:

  1. The browser generates a Turnstile token and includes it in the POST body alongside the form fields.
  2. The Worker receives the request and verifies the Turnstile token with Cloudflare’s server-side API. If verification fails, the Worker returns 400 and stops.
  3. The Worker validates all form fields (name, email, message, topic, age attestation). Invalid submissions return 422.
  4. If a rate-limit rule is exceeded (5 submissions per 15 minutes or 30 per day per IP), the Worker returns 429.
  5. A valid submission is written to the messages_tickets table in the shared D1 database.
  6. If ADMIN_PORTAL_RESEND_API_KEY is set, the Worker sends:
    • A notification email to the studio (internal alert).
    • An auto-acknowledgement email to the submitter for support and general topics.

Step 1: Register a Turnstile site

Turnstile requires a site key (used in the browser) and a secret key (verified server-side). Both are tied to the domain your site runs on.

  1. Log in to the Cloudflare dashboard and open Turnstile in the left sidebar.
  2. Select Add site.
  3. Enter a name (e.g. “serenecreations.io contact form”) and the domain (serenecreations.io).
  4. Choose Managed as the widget type — this provides invisible or minimal-challenge protection without user-visible puzzle boxes.
  5. Select Create.
  6. Copy the Site key and the Secret key.

Add the site key to your project’s .env file:

PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAA...

Rebuild and deploy the site after adding the site key — it is baked into the client-side widget at build time.


Step 2: Set Worker secrets

The secret key and Resend API key must be set as Worker secrets — not plain environment variables, which are visible in the Cloudflare dashboard in plain text.

# Turnstile server-side verification
wrangler secret put TURNSTILE_SECRET_KEY
# Paste the secret key from step 1 when prompted

# Resend sending
wrangler secret put ADMIN_PORTAL_RESEND_API_KEY
# Paste your Resend API key when prompted

Alternatively, set them through the Cloudflare dashboard: Workers & Pages → your-worker → Settings → Variables & Secrets → Add → Secret.


Step 3: Verify your Resend sending domain

Resend requires SPF and DKIM DNS records to be verified before it will send from your domain.

  1. Log in to resend.com and open Domains.
  2. Select Add domain and enter the domain you want to send from (e.g. serenecreations.io).
  3. Resend shows two DNS records to add at your registrar:
    • A TXT record for SPF.
    • A CNAME record for DKIM.
  4. Add both records at your registrar (same process as custom domain setup).
  5. Return to Resend and select Verify DNS records. Verification usually passes within a few minutes.

Once the domain is verified, the from address in Worker emails (e.g. hello@serenecreations.io) will pass SPF and DKIM checks and be less likely to land in spam.


Step 4: Test the integration

With secrets set and the domain verified, submit a test message through the contact form on your site:

  1. Open your site’s /contact page.
  2. Fill in all required fields, check the age attestation, and submit.
  3. Confirm the response is a success message (no error shown).
  4. In the portal, open Messages → Tickets. The submission should appear as a new open ticket within seconds.
  5. Check the notification email address — you should receive an internal alert from Resend.

If a step fails, check the Worker’s real-time log in the Cloudflare dashboard: Workers & Pages → your-worker → Logs. Errors are logged with a descriptive message.


Form fields and validation

Field Required Validation
Name Yes 1–100 characters
Email Yes Valid email format
Topic Yes One of: support, general, feedback, other
Message Yes 10–5000 characters
Age attestation Yes Must be checked; submitter confirms they are 13 or older
Turnstile token Yes Generated client-side; verified server-side

Submissions that fail validation return HTTP 422 with a JSON error body. The form UI surfaces these errors inline.


Rate limiting

The Worker enforces rate limits per IP:

  • 5 submissions per 15 minutes
  • 30 submissions per day

Requests that exceed these limits receive HTTP 429. The limits are designed to block automated form spam while allowing legitimate use.


Next steps