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
- SMTP service configured and enabled in
config.yaml - 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
| Setting | Default | Description |
|---|---|---|
to_email | admin@example.com | Email 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
- Visitor opens
/contact– the template renders the form - Plugin injects a hidden
_csrffield via theon_renderhook - Visitor fills out the form and clicks Send
- Browser POSTs to
/contact-submit– the plugin handles it - Plugin validates: CSRF token, required fields (name, email, message)
- On success: plugin hands the message to the host’s SMTP service through the
host-servicescapability, redirects to/contact-sent - On failure: redirects back to
/contact?error=name(oremail,message,csrf,smtp) - Template reads
request.query.errorto show the appropriate error message
Error Codes
?error= value | Meaning |
|---|---|
name | Name field is empty |
email | Email field is empty or missing @ |
message | Message field is empty |
csrf | CSRF token invalid or expired |
smtp | SMTP 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
| Component | Edition |
|---|---|
| Contact form template | No license (plain HTML) |
Plugin (accent-contact) | Free in accent build + dev serve; Standard to serve in production |
| SMTP service | Standard/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.