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 plugin requires the Standard edition or higher (plugins are not available in Core edition).

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
      server_port: "4440"                  # Port of the accent server (for SMTP bridge)
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.
server_port4440Port where accent serve is running (for the SMTP bridge call)

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 calls the SMTP bridge to send the email, 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 templateAll editions (just HTML)
Plugin (accent-contact)Standard+ (WASM plugins required)
SMTP serviceStandard+ (smtp Cargo feature)

Core edition users see the contact form page but cannot submit it – the plugin system is not available. This provides a natural upgrade path to the Standard edition.