Contact Form#

The contact form on the About page is powered by Formspree.

Why Formspree#

GitHub Pages only serves static files: there is no server to receive a form submission and send an email. Formspree fills that gap. The form posts to a Formspree endpoint, and Formspree:

  • filters spam,

  • forwards each message to my email address,

  • keeps a copy in its dashboard, and

  • shows the visitor a confirmation page.

This keeps my email address off the site, so it cannot be harvested by bots. The free plan has a monthly submission limit, which is plenty for a personal site.

Alternatives considered:

  • mailto link: simplest, but publishes the email address.

  • Google Forms: free and unlimited, but the embedded form uses Google’s styling rather than the site theme.

  • Netlify Forms: built in, but only for sites hosted on Netlify.

Setup#

  1. Sign up at formspree.io and create a new form.

  2. Set the email address that submissions should be forwarded to.

  3. Copy the form’s endpoint, https://formspree.io/f/<form-id>. The form ID is not a secret: it is visible in the page source of any site that uses it.

  4. Add the form to the page (below), rebuild, and send a test message. Formspree asks you to confirm the first submission by email before forwarding any messages.

The form#

Sphinx has no form directive, so the form is plain HTML inside a raw directive in source/about.rst. The classes (form-control, form-label, btn btn-primary) come from Bootstrap, which the PyData theme already includes, so the form matches the site without any extra CSS:

.. _contact:

Contact
-------

.. raw:: html

   <form action="https://formspree.io/f/<form-id>" method="POST" class="mt-3" style="max-width: 36rem;">
     <div class="mb-3">
       <label for="contact-name" class="form-label">Name</label>
       <input type="text" id="contact-name" name="name" class="form-control"
              maxlength="100" required autocomplete="name">
     </div>
     <div class="mb-3">
       <label for="contact-email" class="form-label">Email</label>
       <input type="email" id="contact-email" name="email" class="form-control"
              maxlength="200" required autocomplete="email">
     </div>
     <div class="mb-3">
       <label for="contact-message" class="form-label">Message</label>
       <textarea id="contact-message" name="message" class="form-control"
                 rows="6" maxlength="5000" required></textarea>
     </div>
     <input type="text" name="_gotcha" style="display: none" tabindex="-1" autocomplete="off">
     <button type="submit" class="btn btn-primary">Send message</button>
   </form>

Points to note:

  • name="email" lets Formspree set the reply-to address, so replying to the notification email goes straight to the sender.

  • required, type="email" and maxlength give basic validation in the browser before anything is sent.

  • _gotcha is a honeypot field. It is hidden from people, but spam bots tend to fill in every field; Formspree silently discards any submission where it is not empty.

  • The .. _contact: label lets other pages link straight to the form with the ref role, as the “Contact form” link on the home page does.

Troubleshooting#

“Form not found. Please check the form hashid”

The action URL still contains a placeholder or a mistyped ID. Copy the endpoint again from the Formspree dashboard, rebuild, and reload the page with Ctrl+F5.

Messages are not arriving

Check that the first submission was confirmed, look in the spam folder, and check the Formspree dashboard to see whether the message was received.

Testing locally

Submissions from http://localhost:8000 work the same as from the published site.