Guides Hugo
Contact form for a Hugo site
Hugo builds plain static files, so a contact form needs somewhere to post. The tidy way is a shortcode: write the form once, then drop it into any Markdown page. Submissions go to sendm8 and arrive in your inbox.
Why not paste HTML into Markdown?
Since Hugo 0.60, the Goldmark renderer leaves raw HTML out of Markdown unless you turn on markup.goldmark.renderer.unsafe. A shortcode avoids that setting and keeps the form in one place.
Create the shortcode
Save this as layouts/shortcodes/contact-form.html. Change you@example.com to your own address, or set it in your site config as shown below.
{{ $endpoint := site.Params.formEndpoint | default "https://sendm8.com/f/you@example.com" }}
<form action="{{ $endpoint }}" method="POST">
<label>Your email <input name="email" type="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
<input type="hidden" name="_next" value="{{ "thanks/" | absURL }}">
<button>Send</button>
</form>Then use it in any content file:
---
title: "Contact"
---
Got a question? Send me a message.
{{< contact-form >}}To keep the address in config instead of the template, add it to hugo.toml:
[params]
formEndpoint = "https://sendm8.com/f/you@example.com"Confirm your address
- The first time anyone submits, sendm8 stores the message and emails
you@example.com(well, your address) one confirmation link. Click it and that message, plus anything that arrived meanwhile, is delivered. After that, submissions just arrive. - The
_nextfield sends visitors to/thanks/on your site, built frombaseURL. Create that page, or remove the line to use sendm8’s plain thank-you page. - Messages arrive from
notify@sendm8.com. Reply and you’re answering the visitor directly.
Themes that already have a contact form
Many Hugo themes ship a contact form with a configurable action, often a params entry named after Formspree. Set it to your sendm8 endpoint. sendm8 understands Formspree’s field names (_replyto, _subject, _next, _gotcha), so the theme’s form usually needs no other change.
Taking file uploads
To accept files, add enctype="multipart/form-data" to the form and use a normal <input type="file">. Each file can be up to 5 MB, with 10 MB and 5 files per submission. Files are stored with the submission, and your notification links to them rather than attaching them, so nobody’s inbox fills up.
Spam and limits
Every submission is rate-limited, scored for spam signals and checked by a small AI model. Anything flagged goes to a spam folder rather than your inbox, and it’s deleted after 30 days.
A no-account endpoint keeps up to 100 submissions a month and delivers by email. Sign in with the same address and the form moves into your account: 1,000 submissions a month, a dashboard inbox with search and CSV export, and Discord, Slack, Telegram or webhook delivery.
Questions
- Why does my form disappear when I paste it into a Hugo Markdown file?
- Hugo’s Goldmark renderer drops raw HTML by default. Put the form in a shortcode, or set markup.goldmark.renderer.unsafe to true in your config.
- Does it work with Hugo themes that support Formspree?
- Usually, yes. Set the theme’s form action to your sendm8 endpoint. The Formspree-style underscore fields work the same way.
- Does it work on Netlify, Cloudflare Pages or GitHub Pages?
- Yes. The form posts to sendm8 directly, so it works on any static host.