Contact Page — Form, Email, and Formspree Integration
Set up the Dax Pro contact page: Formspree integration for form submissions, email display, guarantee list, form field customization, and spam protection. Edit via Decap CMS or directly in content/contact.md.
What the Contact Page Does
The contact page is the conversion endpoint of your site. Every visitor who wants to ask a question, request a quote, or hire you lands here. Unlike other pages that are static content, the contact page has a working form that sends submissions somewhere — by default, to Formspree, a free form backend service that emails you each submission.
The contact page has two parts:
- Left column — a headline, a subtext paragraph, a list of guarantees, and an email address
- Right column — a form with configurable fields, submitting to a Formspree endpoint
Both parts are driven by content/contact.md and its frontmatter. The form submission URL is configured in the frontmatter, so you can change the destination without editing any template.
The file is:
content/contact.md
This chapter covers:
1. How the contact page is structured — what each part does
2. Setting up Formspree — creating an endpoint and configuring the form
3. Editing via Decap CMS — the recommended method for most users
4. Editing directly — the developer method
5. Customizing form fields — adding, removing, and configuring inputs
6. Spam protection — honeypot field and other techniques
7. Testing — confirming submissions arrive
How the Contact Page Is Structured
The contact page uses the contact.dax layout, which renders two columns inside a dark gradient box. The left column comes from the contact: section of the frontmatter; the right column comes from the contact: section too, but specifically from the contact.fields list.
The frontmatter of content/contact.md looks like this:
---
layout: "contact.dax"
title: "Enterprise Consultation & Support Desk"
breadcrumb: "Contact"
description: "Discuss your .NET static architecture requirements, Decap CMS integrations, or procurement questions directly with our technical team."
contact:
kicker: "Direct Technical Channel"
heading: "Speak With a .NET Architecture Specialist"
subtext: "We provide hands-on architectural assessments for teams migrating from WordPress, Drupal, Sitecore, or Umbraco to pure .NET static generation with Decap CMS."
guarantees:
- text: "Guaranteed response time: Within 4 business hours"
- text: "Direct Slack / Microsoft Teams channel for Corporate Pro tiers"
- text: "Confidentiality: Mutual NDA provided upon request"
email_label: "Primary Communications:"
email: "[email protected]"
hours: "Operating Hours: 08:00 – 18:00 UTC (Monday – Friday)"
form_id: "dax-contact-page-form"
form_action: "https://formspree.io/f/mnqkqkve"
field1_id: "contact-fullname"
field1_name: "name"
field1_label: "Full Name"
field1_type: "text"
field1_placeholder: "e.g. David Sterling"
field1_required: true
field2_id: "contact-email-addr"
field2_name: "email"
field2_label: "Work Email"
field2_type: "email"
field2_placeholder: "[email protected]"
field2_required: true
field3_id: "contact-interest"
field3_name: "tier"
field3_label: "Inquiry Purpose"
field3_type: "select"
field3_opt1_value: "licensing"
field3_opt1_label: "Commercial Theme Licensing"
field3_opt2_value: "migration"
field3_opt2_label: "CMS Migration to .NET SSG"
field3_opt3_value: "decap"
field3_opt3_label: "Decap CMS Customization"
field3_opt4_value: "azure"
field3_opt4_label: "Azure Static Web Apps Deployment"
field3_opt5_value: "custom"
field3_opt5_label: "Other Technical Request"
field4_id: "contact-details"
field4_name: "message"
field4_label: "Project Details"
field4_type: "textarea"
field4_placeholder: "Describe your existing tech stack, timeline, or particular customization requirements..."
submit: "Submit Enterprise Request"
Every field explained:
| Field | Purpose |
|---|---|
layout |
Always contact.dax. Do not change. |
title |
Page title for and H1 |
breadcrumb |
Short label for breadcrumb |
description |
Meta description for SEO |
contact.kicker |
Small uppercase label above the heading |
contact.heading |
The large heading in the left column |
contact.subtext |
Paragraph below the heading |
contact.guarantees |
List of three bullet points |
contact.email_label |
Small label above the email |
contact.email |
The email address shown |
contact.hours |
Optional operating hours line |
contact.form_id |
HTML id for the form element |
contact.form_action |
URL where form submissions are POSTed |
contact.field1_ through contact.field4_ |
The form fields |
contact.submit |
Submit button label |
The two most important fields are form_action and form_id. The first determines where submissions go; the second is just an HTML identifier (used for styling and potential JavaScript hooks). Change form_action to point at your own Formspree endpoint.
Setting Up Formspree
The default form_action in content/contact.md points at a demo Formspree endpoint (https://formspree.io/f/mnqkqkve). That endpoint is not yours. Submissions go to the Dax Pro team, not to you.
You must replace it with your own Formspree endpoint. The process takes about five minutes.
Step 1: Create a Formspree Account
1. Open formspree.io in a browser
2. Click Sign Up in the top-right corner
3. Enter your email address and a password
4. Confirm your email if Formspree sends a verification link
5. After login, you land on the Formspree dashboard
Step 2: Create a New Form
1. In the dashboard, click New Form (or + New Form depending on the layout)
2. Enter a name for the form — something like Dax Pro Contact Form or Contact Requests
3. Choose the plan. The Free plan allows 50 submissions per month, which is enough for most small sites. If you expect more, choose Gold ($10/month for 1,000 submissions).
4. Confirm the form
Step 3: Get Your Endpoint URL
After creating the form, Formspree shows you the endpoint URL. It looks like:
https://formspree.io/f/xyzabcd
The last part (xyzabcd) is your form ID. This is what you put in contact.form_action.
Copy the full URL.
Step 4: Configure Where Emails Go
1. In the Formspree dashboard, open your form
2. Go to Settings → Emails
3. Add the email address where you want submissions delivered
4. If you want a copy sent to the person who submitted the form (a confirmation email), enable Auto-Responder and fill in the reply template
5. Save the settings
Step 5: Add Your Domain (Recommended)
Formspree requires that submissions come from a domain you have verified. Otherwise, submissions are marked as "unverified" and may be rate-limited or blocked.
1. In the form settings, go to Settings → Domains
2. Click Add Domain
3. Enter your production domain (e.g., example.com, without www. or https://)
4. Formspree will show DNS or HTML verification instructions
DNS verification (recommended):
HTML verification (alternative):
temporarilyIf you skip domain verification, the form still works — submissions arrive — but Formspree marks them as "unverified" in the dashboard. For low-traffic sites this is fine. For production sites, verify the domain.
Step 6: Update contact.form_action
Now update your site's contact page with your new Formspree endpoint.
Via Decap CMS:
1. Open https://yoursite.com/admin/
2. Click ✉ Contact Page
3. Click Contact
4. Scroll to Form Action
5. Replace the URL with your Formspree endpoint (https://formspree.io/f/xyzabcd)
6. Scroll to Email
7. Replace creativebydre@gmail.com with your own email
8. Click Publish
Directly in content/contact.md:
form_action: "https://formspree.io/f/xyzabcd"
email: "hello@yourdomain.com"
Save the file and rebuild.
Step 7: Test a Submission
After the change is live:
1. Open https://yoursite.com/contact/ (or http://localhost:8080/contact/ if you are testing locally, and note that local submissions go directly to Formspree — the endpoint is not domain-restricted if you have not set up domain verification)
2. Fill in the form with test data
3. Click Submit Enterprise Request
4. Wait a few seconds
5. Check the email inbox associated with your Formspree account
6. If the email arrives, the form is working
7. If it does not arrive, check the Formspree dashboard — submissions appear in Submissions tab, and any errors show there
Common issues during test:
localhost and the browser blocks the request due to CORS; test on the deployed site instead, or temporarily allow localhost in Formspree's allowed originsMethod 1 — Editing the Contact Page via Decap CMS
Accessing the Contact Page
1. Open https://yoursite.com/admin/ and log in
2. Click ✉ Contact Page in the left sidebar
3. Click Contact
4. The editor opens
Editing the Header and Left Column
The left column shows the kicker, heading, subtext, guarantees list, email, and hours.
Fields:
| Field | Default | Recommended |
|---|---|---|
| Title | Enterprise Consultation & Support Desk |
Your own page title |
| Breadcrumb | Contact |
Usually Contact |
| Description | Discuss your .NET static architecture... |
One sentence describing what visitors can ask about |
| Contact → Kicker | Direct Technical Channel |
Small label above the heading |
| Contact → Heading | Speak With a .NET Architecture Specialist |
Your main heading |
| Contact → Subtext | We provide hands-on architectural assessments... |
One paragraph explaining what happens after they submit |
| Contact → Guarantees | Three text bullets | Replace with your own (or reduce to one) |
| Contact → Email Label | Primary Communications: |
Small label above the email |
| Contact → Email | creativebydre@gmail.com |
Your actual email |
| Contact → Hours | Operating Hours: 08:00 – 18:00 UTC... |
Your hours, or leave empty |
Guarantees — the list of three bullet points. To edit:
1. Expand Guarantees in the editor
2. Each item has a Text field
3. Edit each text
4. To add a fourth guarantee, click Add Guarantees
5. To remove one, hover and click the trash icon
A single guarantee ("We reply within 24 hours") is often better than three. Choose based on what is true.
Hours — optional. If you do not have set hours, clear the field. The line is hidden when empty.
Editing the Form Action and ID
| Field | Purpose |
|---|---|
| Form ID | HTML id attribute. Default: dax-contact-page-form. Leave as-is unless you have a specific reason to change it. |
| Form Action | URL where the form posts. This is your Formspree endpoint. |
To update form action:
1. Find the Form Action field
2. Paste your Formspree endpoint (https://formspree.io/f/your-id)
3. Click Publish
Editing Form Fields
The form fields are numbered field1 through field4. Each has several sub-fields.
Field 1 — Full Name (default)
| Sub-field | Default | Notes |
|---|---|---|
| Field 1 ID | contact-fullname |
HTML id. Unique. |
| Field 1 Name | name |
HTML name attribute. Used in submission. |
| Field 1 Label | Full Name |
Visible label |
| Field 1 Type | text |
HTML input type |
| Field 1 Placeholder | e.g. David Sterling |
Placeholder text |
| Field 1 Required | true |
Whether the field is required |
Field 2 — Work Email (default)
Similar to Field 1 but with type: "email" and a different placeholder.
Field 3 — Inquiry Purpose (default, a select dropdown)
This field uses type: "select" and has options:
| Sub-field | Default |
|---|---|
| Field 3 ID | contact-interest |
| Field 3 Name | tier |
| Field 3 Label | Inquiry Purpose |
| Field 3 Type | select |
| Field 3 Opt 1 Value | licensing |
| Field 3 Opt 1 Label | Commercial Theme Licensing |
| Field 3 Opt 2 Value | migration |
| Field 3 Opt 2 Label | CMS Migration to .NET SSG |
| Field 3 Opt 3 Value | decap |
| Field 3 Opt 3 Label | Decap CMS Customization |
| Field 3 Opt 4 Value | azure |
| Field 3 Opt 4 Label | Azure Static Web Apps Deployment |
| Field 3 Opt 5 Value | custom |
| Field 3 Opt 5 Label | Other Technical Request |
The value is what gets submitted; the label is what the user sees. For example, if a user selects "Commercial Theme Licensing," Formspree receives tier: licensing.
Field 4 — Project Details (default, a textarea)
| Sub-field | Default |
|---|---|
| Field 4 ID | contact-details |
| Field 4 Name | message |
| Field 4 Label | Project Details |
| Field 4 Type | textarea |
| Field 4 Placeholder | Describe your existing tech stack, timeline... |
Editing the Submit Button
| Field | Default |
|---|---|
| Submit | Submit Enterprise Request |
Change to "Send Message", "Contact Us", or "Request a Quote" to suit your brand.
Publishing
After editing any field, click Publish in the top-right corner. Decap CMS commits the change, the host rebuilds, and the new contact page is live in 30–60 seconds.
Method 2 — Editing the Contact Page Directly
For developers, editing content/contact.md directly in VS Code is faster than the CMS.
Opening the File
code content/contact.md
Editing Specific Fields
Change the email address:
email: "hello@yourdomain.com"
Change the Formspree endpoint:
form_action: "https://formspree.io/f/xyzabcd"
Change the heading:
heading: "Let's Talk About Your Project"
Remove the hours line:
Delete the hours: line. The template hides the line when the field is missing or empty.
Change guarantees from three to two:
guarantees:
- text: "We reply within one business day."
- text: "All conversations are confidential."
To add a fourth:
guarantees:
- text: "We reply within one business day."
- text: "All conversations are confidential."
- text: "No obligation to hire."
- text: "Mutual NDA available on request."
Change a select field's options:
If you want the "Inquiry Purpose" dropdown to have different options, edit field3_opt1_ through field3_opt5_. For example:
field3_opt1_value: "new-project"
field3_opt1_label: "Start a New Project"
field3_opt2_value: "existing-project"
field3_opt2_label: "Discuss an Existing Project"
field3_opt3_value: "migration"
field3_opt3_label: "Migrate from Another CMS"
field3_opt4_value: "support"
field3_opt4_label: "Technical Support"
field3_opt5_value: "other"
field3_opt5_label: "Something Else"
Remove a field entirely:
The template supports four fields. If you want only three, you can:
field4_* fields. The template checks whether each field is defined.field4_id to an empty string (""). The template skips empty fields.Option 1 is cleaner. Delete all field4_id, field4_name, field4_label, field4_type, field4_placeholder.
Add a fifth field:
This requires editing the template (templates/partials/contact/box.dax) to render field 5. Not covered in this chapter. For most sites, four fields are enough.
Rebuilding After Direct Edits
dotnet run -- build
Or, if the development server is running:
dotnet run -- start
The server rebuilds automatically. Refresh the browser to see the change.
For deployed sites:
git add content/contact.md
git commit -m "Update contact form endpoint"
git push
Spam Protection
Public contact forms attract spam. Formspree includes built-in spam filtering (Akismet and reCAPTCHA-style checks), but there are additional steps you can take.
The Honeypot Field (Already Included)
Dax Pro's contact form includes a honeypot field named _gotcha:
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
This field is invisible to human users (via display:none), but bots that auto-fill every field will fill it. Formspree rejects submissions where _gotcha is not empty.
You do not need to do anything to enable this — it is already in the template. Do not remove it.
Formspree's Built-In Protection
Formspree applies multiple anti-spam measures automatically:
The free plan includes these protections. No configuration is needed.
Optional: Add reCAPTCHA
If spam becomes a problem, Formspree supports explicit reCAPTCHA v2 (the "I'm not a robot" checkbox). To enable:
1. In Formspree's form settings, go to Spam
2. Enable reCAPTCHA
3. Formspree shows a To add reCAPTCHA to your Dax Pro form, you would need to edit For most sites, the default honeypot plus Formspree's automatic filtering is enough. Formspree has a "Rules" feature that lets you auto-delete submissions matching certain patterns: 1. In Formspree, go to Settings → Rules 2. Add a rule for messages containing certain spam keywords 3. Choose to mark as spam or auto-delete Use sparingly — aggressive rules may catch legitimate inquiries. You should test the form at least three times: Test 1: Verify the endpoint works. 1. Fill in the form with obvious test data (name: "Test User", email: "test@example.com", message: "This is a test submission") 2. Click submit 3. Wait a few seconds 4. Check the Formspree dashboard — the submission should appear under Submissions 5. Check the destination email — the notification should arrive If the submission does not appear in Formspree, the endpoint URL in Test 2: Verify required fields. 1. Leave the name field empty 2. Fill in the other fields 3. Click submit 4. The browser should show a "Please fill in this field" message next to the name field 5. The form should not submit This confirms that Test 3: Verify required email format. 1. Fill in the email field with invalid data (e.g., 2. Fill in the other fields 3. Click submit 4. The browser should show "Please enter an email address" or similar 5. The form should not submit This confirms the Test 4: Verify the honeypot. 1. Open the form in a browser 2. Right-click the form and choose Inspect (or Inspect Element) 3. Find the hidden input: 4. Use the DevTools to remove the 5. Fill in all fields including the 6. Submit the form 7. Formspree should reject the submission as spam If Formspree rejects the submission, the honeypot is working. Restore the page (refresh) after testing. Mistake 1: Leaving the demo Formspree endpoint. The default Mistake 2: Not verifying the domain in Formspree. Without domain verification, Formspree marks submissions as "unverified." They still arrive, but Formspree may apply stricter rate limits or reject some submissions. Verify your domain for production sites. Mistake 3: Removing the The honeypot is your first line of spam defense. Do not remove it. If you are editing the template, leave it in place. Mistake 4: Using the same Every field's Mistake 5: Mismatched select options. If you edit Mistake 6: Setting The message field ( Mistake 7: Forgetting to update the email in the template. The contact page's template ( Mistake 8: Not testing before deploying. Test submissions on the deployed site, not just locally. Local submissions may succeed while the deployed site fails (or vice versa) due to CORS, domain verification, or DNS issues. After editing, verify: 1. URL works — navigate to 2. Left column — kicker, heading, subtext, guarantees, email, and hours render correctly. 3. Right column — all four form fields render with correct labels, placeholders, and required flags. 4. Select field — clicking the "Inquiry Purpose" dropdown shows all five options. 5. Submit button — labeled as configured. 6. Form action — right-click a field, view source, confirm 7. Test submission — submit valid data, confirm arrival in Formspree and inbox. 8. Mobile — resize to mobile width. Columns stack vertically, form fields expand to full width, submit button is full width. If a submission fails or the email is wrong, check: After this chapter, you can: The contact page is done. The next chapter covers the Services Controller — the file that generates the → Continue to Services Controller tag and a templates/partials/contact/box.dax to include the required HTML and JavaScript. This is a developer task — see the Developer Guide.Optional: Filter Submissions in Formspree
Testing the Contact Form
contact.form_action is wrong. Re-copy it from Formspree.field1_required: true is working.notanemail)type: "email" validation is working.style="display:none" attribute_gotcha field
Common Contact Page Mistakes
form_action: "https://formspree.io/f/mnqkqkve" sends submissions to the Dax Pro team, not to you. Replace it with your own endpoint immediately._gotcha honeypot.field1_id and field2_id.id must be unique within the form. If you copy contact-fullname to another field, browsers produce undefined behavior (labels point to the wrong input).field3_opt1_label but forget to update field3_opt1_value, the label shows one thing and the submission sends another. Both must be edited together.required: true on the message field.field4) is usually optional. If you make it required, some users abandon the form because they do not know what to write. Leave it optional.templates/partials/contact/box.dax) uses {{ contact.email }}. Changing contact.email in the frontmatter updates the displayed email. But if you also hardcoded an email in the template (which you should not have), you must update both.
Verifying Your Contact Page
/contact/. The page loads. has action attribute pointing to your Formspree endpoint.contact.form_action in the frontmatter
What You Have Now
content/contact.md/services/ listing page and handles pagination for service entries.