Contact Form Plugin

Overview

The accent-contact plugin adds a contact form that validates submissions, protects against CSRF attacks, and forwards messages to your inbox via the built-in SMTP service.

The accent-contact plugin runs in accent build and dev serve with no license. Because it emails submissions via the SMTP service, serving live submissions in production requires a Standard or Pro license.

Prerequisites

  1. SMTP service configured and enabled in config.yaml
  2. Plugins enabled in config.yaml:
    plugins:
      enabled: true
      directory: "./plugins"
    

Installation

Copy the plugin files into your site’s plugins/ directory:

your-site/
  plugins/
    accent-contact/
      plugin.toml    # Plugin configuration
      plugin.wasm    # Compiled plugin binary

Distribution of the packaged plugin through the public plugin hub is planned; the sections below document its behaviour and configuration.

Configuration

The plugin reads settings from config.yaml under the plugin’s config section:

plugins:
  enabled: true
  directory: "./plugins"
  config:
    accent-contact:
      to_email: "hello@example.com"       # Destination inbox
      subject_prefix: "[Contact] "         # Prefix for email subjects
      csrf_secret: "your-random-secret"    # Secret for CSRF token generation
SettingDefaultDescription
to_emailadmin@example.comEmail address that receives form submissions
subject_prefix[Contact] Prefix added to the email subject line
csrf_secret(built-in default)Secret key for CSRF token generation. Set a unique value per site.

Mail goes through the typed host-services capability, so the plugin needs no server_port and no allowed_hosts entry.

Content Pages

Create two content pages for the form and the thank-you message:

content/contact/default.md:

---
title: Contact Us
template: contact
lead: Have a question? Send us a message.
menu:
  visible: true
---

We'd love to hear from you. Fill out the form below.

content/contact-sent/default.md:

---
title: Message Sent
menu:
  visible: false
---

Thank you for your message! We'll get back to you within 24 hours.

[Back to homepage](/)

Template

Create a contact.html.jinja template in your theme:

{% extends "base.html.jinja" %}

{% block content %}
<article>
  <h1>{{ page.title }}</h1>
  {{ page.content | safe }}

  {# Error messages from form validation #}
  {% if request.query.error == "name" %}
    <div class="alert error">Please enter your name.</div>
  {% elif request.query.error == "email" %}
    <div class="alert error">Please enter a valid email address.</div>
  {% elif request.query.error == "message" %}
    <div class="alert error">Please enter a message.</div>
  {% elif request.query.error == "csrf" %}
    <div class="alert error">Security token expired. Please try again.</div>
  {% elif request.query.error == "smtp" %}
    <div class="alert error">Unable to send. Please try again later.</div>
  {% elif request.query.error %}
    <div class="alert error">Something went wrong. Please try again.</div>
  {% endif %}

  <form action="/contact-submit" method="POST">
    {# CSRF token is injected automatically by the plugin #}
    <label>Name <input name="name" required></label>
    <label>Email <input name="email" type="email" required></label>
    <label>Message <textarea name="message" required rows="6"></textarea></label>
    <button type="submit">Send Message</button>
  </form>
</article>
{% endblock %}

The plugin’s on_render hook automatically injects a hidden CSRF token field into every <form> element. You do not need to add it manually.

How It Works

  1. Visitor opens /contact – the template renders the form
  2. Plugin injects a hidden _csrf field via the on_render hook
  3. Visitor fills out the form and clicks Send
  4. Browser POSTs to /contact-submit – the plugin handles it
  5. Plugin validates: CSRF token, required fields (name, email, message)
  6. On success: plugin hands the message to the host’s SMTP service through the host-services capability, redirects to /contact-sent
  7. On failure: redirects back to /contact?error=name (or email, message, csrf, smtp)
  8. Template reads request.query.error to show the appropriate error message

Error Codes

?error= valueMeaning
nameName field is empty
emailEmail field is empty or missing @
messageMessage field is empty
csrfCSRF token invalid or expired
smtpSMTP service failed to send

Email Format

The plugin sends multipart emails with both plain text and HTML bodies:

Plain text:

Name: John Doe
Email: john@example.com

Message:
Hello, I have a question about...

HTML:

<h2>Contact Form Submission</h2>
<p><strong>Name:</strong> John Doe</p>
<p><strong>Email:</strong> john@example.com</p>
<hr>
<p>Hello, I have a question about...</p>

Local Testing

Use Mailpit to catch emails locally:

# Install
brew install mailpit

# Start (SMTP on port 1025, web UI on port 8025)
mailpit

Configure SMTP for Mailpit in config.yaml:

smtp:
  enabled: true
  host: "127.0.0.1"
  port: 1025
  from: "dev@localhost"
  tls: none

Submit the form and check http://localhost:8025 to see the captured email.

Edition Requirements

ComponentEdition
Contact form templateNo license (plain HTML)
Plugin (accent-contact)Free in accent build + dev serve; Standard to serve in production
SMTP serviceStandard/Pro binary; Standard to send mail in production

Without a license, you can build the site and test form submissions in dev serve. Serving live submissions in production requires a Standard or Pro license.