Skip to main content

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 ContactForm shared template component.
  • Only visible for churches with an active premium_churches record.

Step 1: Visitor Fills the Form

The contact form collects:

FieldRequiredValidation
NameYesMust not be empty
EmailYesMust match email regex pattern
PhoneNoNo validation
MessageYesMust be at least 10 characters
Website (honeypot)NoHidden 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:

  1. Honeypot field: A hidden website input 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.

  2. Rate limiting: In-memory rate limit of 1 request per 60 seconds per IP address.

    • The IP is extracted from the x-forwarded-for header (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."

Step 3: Look Up the Church Admin

  1. Query premium_churches using supabaseAdmin (service role, bypasses RLS) to find the record matching church_id.
  2. Select: id, admin_email, contact_cc_email, custom_name, church_id, and the related churches.name.
  3. IF no premium record exists OR no admin_email is set:
    • Return a 400 error: "Unable to deliver message. Please try calling the church directly."
    • This prevents messages to unclaimed or misconfigured churches.
  4. Build the recipient list:
    • Primary recipient: admin_email.
    • CC recipient: contact_cc_email (if set and different from admin_email).

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:

FieldRequiredValidation
NameYesMust not be empty
EmailYesMust match email regex pattern
SubjectYesMust not be empty
MessageYesMust be at least 10 characters
Website (honeypot)NoHidden field -- must be empty

Step 2: Security Checks

Three layers of protection:

  1. CSRF origin validation: validateOrigin(request) checks that the request's Origin or Referer header matches the expected site URL. Returns 403 if the origin does not match.

  2. Honeypot field: Same as church contact -- hidden website field. If filled, return silent success.

  3. 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

AspectChurch Contact (/api/contact/church)General Contact (/api/contact)
PurposeVisitor contacts a specific churchVisitor contacts PewSearch support
RecipientChurch admin email (from premium_churches)PewSearch admin (env variable)
CC supportYes (contact_cc_email field)No
Subject fieldNo (auto-generated from sender name)Yes (visitor provides subject)
CSRF checkNo (origin validation not implemented)Yes (validateOrigin)
Stored in DBYes (contact_submissions table)No
Dashboard metricsYes (feeds Overview tab contact count)No
Reply-ToVisitor's emailVisitor's email

Dashboard Integration

Church admins see contact form activity in their admin dashboard at churchwiseai.com/admin/{token} (Overview tab):

  1. The Overview tab fetches metrics via /api/admin/overview-stats.
  2. The stats include a count of contact_submissions for the church.
  3. This helps church admins understand engagement with their Pro Website.
  4. Individual message content is stored in the contact_submissions table but is not currently surfaced in the dashboard UI (only the count is displayed).

Error Scenarios

ScenarioResponseUser sees
Bot fills honeypot200 { ok: true }Silent success (bot thinks it worked)
Rate limit exceeded429"Please wait a moment before sending another message."
Church has no admin email400"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 format400"A valid email address is required."
Message too short400"Message must be at least 10 characters."
Missing name400"Name is required."
Missing church ID400"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) or PewSearch (general)
  • API key: stored in RESEND_API_KEY environment variable
  • Reply-to: always set to the visitor's email so admins can respond directly from their inbox