Knowledge > Processes > Contact Form Flow
Contact Form Flow
PewSearch has two distinct contact forms: the church-specific contact form (for Pro Website pages) and the general PewSearch contact form (for contacting PewSearch support). This document covers both.
Overview
┌─────────────────────────────────────────────┐
│ Church-Specific Contact (/api/contact/church) │
│ │
│ Visitor on Pro Website ──> fills form │
│ │ │
│ ├── honeypot check (bot filter) │
│ ├── rate limit (1/60s per IP) │
│ ├── lookup church admin email │
│ ├── send notification to church admin │
│ ├── send confirmation to visitor │
│ └── store in contact_submissions │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ General PewSearch Contact (/api/contact) │
│ │
│ Visitor on /contact ──> fills form │
│ │ │
│ ├── CSRF origin validation │
│ ├── honeypot check (bot filter) │
│ ├── rate limit (1/60s per IP) │
│ ├── send notification to PewSearch admin│
│ └── send confirmation to visitor │
└─────────────────────────────────────────────┘
Part 1: Church-Specific Contact Form
This form appears on Pro Website pages (churches with an active Pro Website subscription). It lets visitors contact a church directly through the PewSearch platform.
Where It Appears
- On the church's Pro Website page rendered by
UnifiedTemplate. - Specifically in the
ContactFormshared template component. - Only visible for churches with an active
premium_churchesrecord.
Step 1: Visitor Fills the Form
The contact form collects:
| Field | Required | Validation |
|---|---|---|
| Name | Yes | Must not be empty |
| Yes | Must match email regex pattern | |
| Phone | No | No validation |
| Message | Yes | Must be at least 10 characters |
| Website (honeypot) | No | Hidden field -- must be empty |
The form also sends the churchId as a hidden field, identifying which church the message is for.
Step 2: Spam Prevention
Two layers of spam prevention run before any processing:
-
Honeypot field: A hidden
websiteinput field. Bots auto-fill all fields, including this invisible one. IF the honeypot field contains any text, the API returns{ ok: true }immediately (silent success to avoid tipping off the bot). No email is sent, no record is created. -
Rate limiting: In-memory rate limit of 1 request per 60 seconds per IP address.
- The IP is extracted from the
x-forwarded-forheader (standard for Vercel deployments). - IF the same IP submits again within 60 seconds, the API returns a 429 with message "Please wait a moment before sending another message."
- The IP is extracted from the
Step 3: Look Up the Church Admin
- Query
premium_churchesusingsupabaseAdmin(service role, bypasses RLS) to find the record matchingchurch_id. - Select:
id,admin_email,contact_cc_email,custom_name,church_id, and the relatedchurches.name. - IF no premium record exists OR no
admin_emailis set:- Return a 400 error: "Unable to deliver message. Please try calling the church directly."
- This prevents messages to unclaimed or misconfigured churches.
- Build the recipient list:
- Primary recipient:
admin_email. - CC recipient:
contact_cc_email(if set and different fromadmin_email).
- Primary recipient:
Step 4: Send Notification Email to Church Admin
Using Resend, send an email to the church admin (and CC if configured):
- From:
{churchName} via PewSearch <hello@pewsearch.com> - To: admin email (+ CC if set)
- Reply-To: the visitor's email address (so the admin can reply directly)
- Subject:
New message from {visitor name} - Body: Branded HTML email with:
- "New Website Message" header
- "Someone reached out through your Pro Website" subtitle
- Table showing: Name, Email (clickable mailto link), Phone (clickable tel link, if provided), Message
- Footer note: "This message was sent from your Pro Website on PewSearch. Simply reply to this email to respond directly to {name}."
Step 5: Send Confirmation Email to Visitor
A confirmation email is sent to the visitor (fire-and-forget -- failure does not block the response):
- From:
{churchName} via PewSearch <hello@pewsearch.com> - To: the visitor's email
- Subject:
We received your message -- {churchName} - Body: Branded HTML email with:
- "Thank you, {name}!" header
- Confirmation that someone from the church will be in touch
- Copy of their original message in a gray box
- Footer: "This is an automated confirmation from {churchName}'s website on PewSearch."
Step 6: Store Submission for Dashboard Metrics
The submission is saved to the contact_submissions table (fire-and-forget):
INSERT INTO contact_submissions (church_id, name, email, message)
This data feeds the church admin dashboard's Overview tab metrics, specifically the "Contact Messages" count. The dashboard shows how many contact form submissions the church has received.
Step 7: Return Success
IF everything succeeds, return { ok: true }.
IF the Resend email send fails, return a 500 error: "Failed to send message. Please try again."
Part 2: General PewSearch Contact Form
This form is on the /contact page and lets visitors contact the PewSearch team directly (support questions, partnership inquiries, etc.).
Step 1: Visitor Fills the Form
The contact form collects:
| Field | Required | Validation |
|---|---|---|
| Name | Yes | Must not be empty |
| Yes | Must match email regex pattern | |
| Subject | Yes | Must not be empty |
| Message | Yes | Must be at least 10 characters |
| Website (honeypot) | No | Hidden field -- must be empty |
Step 2: Security Checks
Three layers of protection:
-
CSRF origin validation:
validateOrigin(request)checks that the request'sOriginorRefererheader matches the expected site URL. Returns 403 if the origin does not match. -
Honeypot field: Same as church contact -- hidden
websitefield. If filled, return silent success. -
Rate limiting:
checkRateLimit('contact', ip, 1, 60_000)-- 1 request per 60 seconds per IP. Returns 429 if exceeded.
Step 3: Send Notification to PewSearch Admin
Uses sendContactNotificationEmail() from the email library:
- Sends to the PewSearch admin email (configured in environment variables).
- Includes sender name, email, subject, message, and sender IP for abuse tracking.
Step 4: Send Confirmation to Visitor
Uses sendContactConfirmationEmail() (fire-and-forget):
- Confirms receipt of the message.
- Includes the original subject and message for the visitor's reference.
Step 5: Return Success
Return { ok: true } on success.
Differences Between the Two Contact Forms
| Aspect | Church Contact (/api/contact/church) | General Contact (/api/contact) |
|---|---|---|
| Purpose | Visitor contacts a specific church | Visitor contacts PewSearch support |
| Recipient | Church admin email (from premium_churches) | PewSearch admin (env variable) |
| CC support | Yes (contact_cc_email field) | No |
| Subject field | No (auto-generated from sender name) | Yes (visitor provides subject) |
| CSRF check | No (origin validation not implemented) | Yes (validateOrigin) |
| Stored in DB | Yes (contact_submissions table) | No |
| Dashboard metrics | Yes (feeds Overview tab contact count) | No |
| Reply-To | Visitor's email | Visitor's email |
Dashboard Integration
Church admins see contact form activity in their admin dashboard at churchwiseai.com/admin/{token} (Overview tab):
- The Overview tab fetches metrics via
/api/admin/overview-stats. - The stats include a count of
contact_submissionsfor the church. - This helps church admins understand engagement with their Pro Website.
- Individual message content is stored in the
contact_submissionstable but is not currently surfaced in the dashboard UI (only the count is displayed).
Error Scenarios
| Scenario | Response | User sees |
|---|---|---|
| Bot fills honeypot | 200 { ok: true } | Silent success (bot thinks it worked) |
| Rate limit exceeded | 429 | "Please wait a moment before sending another message." |
| Church has no admin email | 400 | "Unable to deliver message. Please try calling the church directly." |
| Email send fails (Resend error) | 500 | "Failed to send message. Please try again." |
| Invalid email format | 400 | "A valid email address is required." |
| Message too short | 400 | "Message must be at least 10 characters." |
| Missing name | 400 | "Name is required." |
| Missing church ID | 400 | "Missing church ID" |
| CSRF origin mismatch (general only) | 403 | "Forbidden" |
Email Delivery Configuration
Both forms send email via Resend (transactional email service):
- Sender domain:
hello@pewsearch.com - Sender name:
{churchName} via PewSearch(church contact) orPewSearch(general) - API key: stored in
RESEND_API_KEYenvironment variable - Reply-to: always set to the visitor's email so admins can respond directly from their inbox