DEV Community

Winni Neessen
Winni Neessen

Posted on

Sending (form-)mails from static websites with Go

From HTML to Wordpress to Hugo

For over 7 years, I've been maintaining the website of a non-profit organisation. When I took over this task from the previous webmaster, the page was a collection of manually generated and maintained HTML pages. Since I didn’t want to manually write HTML on each page for every change that needed to be done, I decided to rebuild the whole site in Wordpress.

Even though this worked pretty well, over the years I started to dislike Wordpress. Also with all the needed plugins, the site became horribly slow (4-5 seconds per requests - I’ve never really figured out which plugin caused this slowness). So eventually I decided to replace the Wordpress with a static page generator: Hugo.

Sending mails from HTML forms

Hugo is great and fast and I really enjoy working with it. It was the right decision. Now, the page is responsive, fast and easy to maintain.

Yet, one thing I couldn’t easily accomplish with Hugo was sending mails from contact forms - basically a form mailer. With Wordpress, since it’s PHP under the hood on the server side, this is pretty easy. But with Hugo, the pages are static HTML.

So I had to use JavaScript and some kind of form mailer to send contact form requests. There are 3rd party services like https://formspree.io/ that are specialized on this, but since I don’t wanna invest money just to send forms and I also don’t trust the data privacy of such services (GDPR is a big thing in the EU you know 😉), I decided to write my own little web service for it.

Introducing js-mailer

js-mailer is a very simple, yet powerful, web service writting in Go that allows static websites to send forms via JavaScript’s fetch() or XMLHttpRequest APIs.

It follows a two-step workflow. First your JavaScript requests a token from the API using the /api/v1/token endpoint. If the request is valid and website is authorized to request a token, the API will respond with a TokenResponseJson. This holds some data, which needs to be included into your form as hidden inputs. It will also provide a submission URL endpoint /api/v1/send/<formid>/<token> that can be used as action in your form. Once the form is submitted, the API will then validate that all submitted data is correct and submit the form data to the configured recipients.

What started as a quick and dirty “I want to send HTML forms as mail” service, quickly transformed into a fully flegged web service. At the time of this post, the service supports the following features:

  • Single-binary webservice
  • Multi-form support
  • Multiple recipients per form
  • Only display form-fields that are configured in for the form in the resulting mail
  • Check for required form fields
  • Anti-SPAM functionality via built-in, auto-expiring and single-use security token feature
  • Anti-SPAM functionality via honeypot fields
  • Limit form access to specific domains
  • Per-form mail server configuration
  • hCaptcha support
  • reCaptcha v2 support
  • Form field type validation (text, email, number, bool)
  • Confirmation mail to poster
  • Custom Reply-To header based on sending mail address

It is FOSS and can be found on Github. Maybe it can help you with a similar problem.

Sending mails with Go

In the first iterations of js-mailer I used the popular go-gomail/gomail package for handling the mail sending part. Unfortunately the project isn't maintained anymore and uses some pretty outdated opinions on how to handle the mail sending.

Due to the above mentioned reasons and the lack of proper alternatives decided to write my own Go mail library. On a long weekend I started working on wneessen/go-mail and in the night of Sunday I had a kind of working prototype and I implemented it with js-mailer.

Meanwhile go-mail has grown to what I would call a pretty comprehensive library for all kinds of "sending mails with Go" tasks and the Github issues page shows that people start using it and request things they'd like to see as part of go-mail. The feature list of go-mail currently holds these pinpoints:

  • Only Standard Library dependant
  • Modern, idiomatic Go
  • Sane and secure defaults
  • Explicit SSL/TLS support
  • Implicit StartTLS support with different policies
  • Makes use of contexts for a better control flow and timeout/cancelation handling
  • SMTP Auth support (LOGIN, PLAIN, CRAM-MD)
  • RFC5322 compliant mail address validation
  • Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, Priority, etc.)
  • Reusing the same SMTP connection to send multiple mails
  • Support for attachments and inline embeds (from file system, io.Reader or embed.FS)
  • Support for different encodings
  • Support sending mails via a local sendmail command
  • Message object satisfies io.WriteTo and io.Reader interfaces
  • Support for Go's html/template and text/template (as message body, alternative part or attachment/emebed)
  • Output to file support which allows storing mail messages as e. g. .eml files to disk to open them in a MUA

So if you need to send mails in Go, I recommend giving it a quick look and see if it can help you accomlish your "mail tasks" easier. Feedback, bug reports, fixed and PRs are of course always welcome.

Top comments (0)