Introduction

Statically generated sites are fast and flexible but don’t have the ability to send contact us emails due to the lack of a backend. A simple solution would be using a service like Airform, Mailgun, or Formspree, but where’s the fun in that? Instead, we can build a simple Express.js API that will handle POST requests from a form and will in turn forward that email to a mailbox.

Frontend

First of all make sure you have a working form. Create the fields you need, give a descriptive id to all the elements, and omit any action or method attributes.


<form id="contactForm">
    <div class="mb-6">
        <label class="form-label" for="name">
            Όνομα <span class="text-red-500">*</span>
        </label>
        <input
                class="form-input"
                id="name"
                name="name"
                placeholder="Frida Kahlo"
                type="text"/>
    </div>
    ...
</form>

Then for the scripting part we are going to set an Event Listener on the form and listen for any submit events. Make sure to include mode: "cors" as we will be filtering request on the server side based on the origin of the request.

const endpoint = '{{ site.Params.contact_form_action }}';
const contactForm = document.getElementById('contactForm');

contactForm.addEventListener('submit', event => {
    event.preventDefault();

    const data = new FormData(contactForm);
    data.set('serverEmail', 'info@antamacollective.gr');

    fetch(endpoint, {
        method: "POST",
        mode: "cors",
        body: data,
    }).then(response => {
        if (response.status === 200) {
            document.getElementById('successful-email').classList.remove('hidden');
        } else {
            document.getElementById('failed-email').classList.remove('hidden');
        }
        document.getElementById('submitterButton').setAttribute('disabled', '');
    });
});

Backend

The backend is a simple Node.js server with Express.js. One integral part is CORS, which only allows specific domains to access the POST path. In sort, CORS is an HTTP-header based mechanism that allows a server to indicate any origins other than its own from which a browser should permit loading resources. This is simply done in Express with the cors middleware. The configuration is simple enough, as seen in the code snippet bellow, you just have to define the allowed origins. The default configuration is adequate for most cases.

const corsOptions = {
    origin: ['https://antamacollective.gr'],
}

app.get('/send-email', cors(corsOptions), (req, res) => {
    res.status(200).send('This route is only visible from CORS allowed origin.');
});

The /send-email route is responsible for posting the email. There are two middleware, multer which decodes multipart/form-data and cors which was explained in the section above. Within the function the fields are validated against a Joi schema and then the email is sent with Nodemailer.

app.post('/send-email', cors(corsOptions), multer().none(), async (req, res) => {
    try {
        const {name, email, message, serverEmail} = req.body;
        const host = req.hostname;

        //Validate against our schema
        await emailSchema.validateAsync({name, email, message});

        const mail = {
            from: email_user,
            replyTo: email,
            to: serverEmail,
            subject: `Email from ${host} - inquiry from: ${name}`,
            text: message
        };

        transporter.sendMail(mail, (err, info) => {
            if (err) {
                res.sendStatus(500);
                return;
            }

            res.sendStatus(200);
        });
    } catch (err) {
        res.sendStatus(400);
    }
});

Repositories