Documentation Index
Fetch the complete documentation index at: https://docs.weaverse.io/llms.txt
Use this file to discover all available pages before exploring further.
Contact Form with Klaviyo
Introduction
This guide shows you how to build a contact form as a Weaverse section whose submissions are emailed to a notification inbox through a Klaviyo API-based transactional flow. Instead of sending mail directly from your storefront (which requires an SMTP provider, deliverability tuning, and secret management), the form fires a single Klaviyo event. A Klaviyo flow listens for that event’s metric and renders a transactional email. You get spam protection (hCaptcha), a tamper-resistant recipient, and a merchant-editable form — without running your own mail infrastructure.Architecture at a glance
profile.email is your notification inbox; the submitter’s details ride along as event properties that the flow email template renders.
Prerequisites
Klaviyo private API key
A private key with the
events:write scope (Full access also works but is broader than needed). Create it in Klaviyo under Settings → API keys → Create Private API Key → Custom Key. You only see the key once.hCaptcha keys
A site key and secret key from hCaptcha. The Pilot theme already uses hCaptcha for the newsletter form, so you can reuse the same pair.
Step 1 — Create the API route
The form posts to a resource route atapp/routes/api/contact.ts. The action does three things: verify the captcha, resolve a safe recipient, then POST a Klaviyo event. Only the parts that carry the integration logic are shown below — the rest (reading form fields, 400s for missing email/message/captcha) is routine.
Resolve a safe recipient. The section’s recipient arrives as a tamperable form field, so it’s only honored when it passes the optional allowlist (see the security model):
profile.email is the recipient (your inbox), and the submitter’s data goes in properties:
useFetcher:
Why the Klaviyo payload looks like this
| Payload part | Purpose |
|---|---|
metric.data.attributes.name | The metric your Klaviyo flow triggers on. Klaviyo auto-creates it on first event. Configurable per-section so one codebase can power multiple flows. |
profile.data.attributes.email | The recipient of the resulting transactional email — your notification inbox, not the submitter. |
attributes.properties.* | Submitter data exposed to the flow email template as {{ event.<key> }} variables. |
revision header | Klaviyo API version pin. Keep it explicit so a server-side API change can’t silently alter behavior. |
Step 2 — Register the route
Add the route inside theapi prefix block in app/routes.ts:
POST /api/contact (and /:locale/api/contact).
Step 3 — Build the Weaverse section
The section atapp/sections/contact-form/index.tsx is a standard Weaverse component — a default-exported component plus a schema. Below are the integration-specific pieces; the rest is ordinary form markup (name / email / message / optional checkbox / dropdown).
Submit with useFetcher and gate on the captcha token:
metric and recipient as hidden fields plus the captcha token:
metricName and recipientEmail in the schema so merchants can point the form at the right Klaviyo flow and inbox without touching code (other inputs — labels, dropdown options, button text — follow the same pattern):
The hCaptcha site key is read from the root loader (
rootData.hCaptchaSiteKey, sourced from PUBLIC_HCAPTCHA_SITE_KEY). The Pilot theme already exposes this for its newsletter form. If your root loader does not return it yet, add it there — the section renders without a captcha when the key is absent, and the server rejects captcha-less submissions, so you’d be blocked until the key is wired up.Step 4 — Register the section
Add the section toapp/weaverse/components.ts so the Weaverse builder can discover it:
Step 5 — Declare the environment types
Add the new variables to theEnv interface in env.d.ts so context.env.* is typed:
Step 6 — Configure the Klaviyo flow
Code alone won’t deliver mail — the event needs a flow that produces a transactional email.Trigger the metric once
Submit the form once (or use the curl smoke test below) so the
Contact Form Submission metric appears in Analytics → Metrics. You can also pre-create it to build the flow first.Create the flow
Flows → Create Flow → Create from Scratch. Set the trigger to Metric → Contact Form Submission. Leave the trigger filter empty so every submission emails you. Drag a Flow Email action in right after the trigger.
Mark the email transactional
Edit email → Settings → Apply for transactional status / This is a transactional email → ON. This is what lets the email deliver regardless of marketing consent. Then set Smart Sending OFF — every submission goes to the same inbox, so smart sending would silently drop repeat notifications inside its window.
Event property → template variable map
Form field (name=) | Event property | Template variable |
|---|---|---|
name | sender_name | {{ event.sender_name }} |
email | sender_email | {{ event.sender_email }} |
phone | sender_phone | {{ event.sender_phone }} |
subject | subject | {{ event.subject }} |
message | message | {{ event.message }} |
inquiry_type | inquiry_type | {{ event.inquiry_type }} |
urgent | is_urgent (bool) | {{ event.is_urgent }} |
| — (server-set) | source_url | {{ event.source_url }} |
| — (server-set) | submitted_at | {{ event.submitted_at }} |
Security model: the recipient allowlist
The “Notification recipient email” section setting is convenient for merchants but is a public, tamperable input — it travels as a normal form field, so anyone can POST/api/contact with an arbitrary recipient. Without a guard, your endpoint becomes an open mail relay.
resolveRecipient() resolves the address in this order:
- The section’s
recipientfield — only if it’s a valid email and (whenCONTACT_FORM_ALLOWED_RECIPIENTSis set) it appears in that allowlist. CONTACT_FORM_RECIPIENT_EMAIL(server-side env).- The hardcoded
FALLBACK_RECIPIENTconstant.
Step 7 — Test end to end
- Open the page on the storefront (or
npm run dev), fill the form, complete hCaptcha, submit. - The fetch to
/api/contactshould return 202; the form resets and the success banner shows. - In Klaviyo: Analytics → Metrics → Contact Form Submission shows the event within ~30s, and Flows → your flow → Analytics shows one recipient processed.
- Check the resolved recipient inbox.
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
Missing KLAVIYO_PRIVATE_API_TOKEN | Env var not set on Oxygen or not pulled locally. Re-run npx shopify hydrogen env pull. |
Captcha verification is required | PUBLIC_HCAPTCHA_SITE_KEY not exposed at runtime, or the root loader doesn’t return hCaptchaSiteKey. Verify the PUBLIC_ prefix. |
| 403 from Klaviyo | The private key is missing the events:write scope. |
| 202 but no email | Flow still in Draft, email not marked transactional, or Smart Sending filtered the repeat send. |
| Event arrives but flow doesn’t fire | Section’s Klaviyo metric name doesn’t match the metric the flow triggers on. |
| Email arrives but variables are blank | Template uses wrong property names — they must be event.sender_name, event.sender_email, event.sender_phone, event.subject, event.message, event.inquiry_type, event.is_urgent, event.source_url, event.submitted_at. |
| ”Update this action to align with your branded sending domain” | The flow email’s from address isn’t on a verified branded sending domain. Complete domain DNS verification in Klaviyo. |
| Notifications going to the wrong/unexpected inbox | The section recipient failed the allowlist (or is invalid) and fell back to CONTACT_FORM_RECIPIENT_EMAIL / FALLBACK_RECIPIENT. Check CONTACT_FORM_ALLOWED_RECIPIENTS. |
Related
Creating Components
Weaverse section fundamentals — the default export + schema pattern used here.
Custom Routing
How resource routes like
/api/contact are defined and matched.Third-party Integrations
General patterns for wiring external APIs into Weaverse themes.
Input Settings
Every schema input type used in the contact form’s settings.
Reference links
- Klaviyo: Guide to setting up API-based transactional events
- Klaviyo API: Create Event
- Klaviyo: Retrieve API credentials / scopes
- Klaviyo help: Sending transactional email content
- Klaviyo help: Use event variables in flow emails
- hCaptcha: Server-side verification
- Shopify: Hydrogen environment variables