Oliver
Post

Filtering out the noise from my contact form: Part II

Tuesday 10 December 2024

Having another go at improving spam filtering on this website!

A few months ago, I published this article, exploring how I tried to update the form handling on this website to move away from Netlify Forms. If you haven't read it, I eventually (after a lot of toing and froing) decided just to stick with Netlify Forms... The addition of a 'Honeypot' field helped to weed out most of the unwanted spam, and Netlify's filtering also ensured that none of it ever reached my inbox, so I was content.

However, in the last few months, I've been pondering a wider shift in how I host this site. Experimenting with Cloudflare Pages showed me just how much faster they are compared to Netlify.
While I pondered this larger change, I decided to move away from Netlify Forms, the one thing locking me in to the Netlify platform.
After a fair bit of trial and error and faffing, I managed to use Cloudflare's documentation to cobble together a worker to process the contact form and send it to my email using Sendgrid.

// src/index.js
var src_default = {
async fetch(request) {
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const contentType = request.headers.get("Content-Type");
if (!contentType || !contentType.includes("application/x-www-form-urlencoded") && !contentType.includes("multipart/form-data")) {
return new Response("Invalid Content-Type. Must be application/x-www-form-urlencoded or multipart/form-data.", { status: 400 });
}
return handleRequest(request);
}
};
async function handleRequest(request) {
const formData = await request.formData();
const turnstileToken = formData.get("cf-turnstile-response");
const userEmail = formData.get("email");
const name = formData.get("name");
const message = formData.get("message");
const isHuman = await verifyTurnstile(turnstileToken);
if (!isHuman) {
return new Response(`
<script>
alert("Verification failed. Make sure to tick the box at the bottom!");
window.location.href = 'https://oliverhewitt.co.uk/contact/'; // Change to your contact form page path
<\/script>
`, { headers: { "Content-Type": "text/html" }, status: 403 });
}
const recipient = "[OLIVERS-EMAIL]";
const subject = `New form submission from ${name}`;
const body = `Name: ${name}
Email: ${userEmail}
Message: ${message}`;
try {
await sendEmail(recipient, subject, body, userEmail);
return new Response(null, {
status: 302,
// 302 Found (temporary redirect)
headers: {
"Location": "https://oliverhewitt.co.uk/success/"
// Change to your desired redirect URL
}
});
} catch (error3) {
console.error("Failed to send email:", error3);
return new Response("Failed to send email.", { status: 500 });
}
}
__name(handleRequest, "handleRequest");
async function verifyTurnstile(token) {
const turnstileSecretKey = "[TURNSTILE-SECRET-KEY]";
const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
secret: turnstileSecretKey,
response: token
})
});
const result = await response.json();
return result.success;
}
__name(verifyTurnstile, "verifyTurnstile");
async function sendEmail(recipient, subject, body, replyToEmail) {
const sendGridApiKey = "[SENDGRID-API-KEY]";
const fromEmail = "noreply@helloiamoliver.co.uk";
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${sendGridApiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
personalizations: [{
to: [{ email: recipient }],
subject
}],
from: { email: fromEmail },
content: [{
type: "text/plain",
value: body
}],
reply_to: { email: replyToEmail }
})
});
if (!response.ok) {
throw new Error(`SendGrid API request failed: ${response.statusText}`);
}
}
__name(sendEmail, "sendEmail");
export {
src_default as default
};
//# sourceMappingURL=index.js.map

In order to combat spam more effectively, I chose to use Cloudflare Turnstile to provide a CAPTCHA-like service to ensure that only humans can get through! After trialling it locally, I launched it on my website.

It works seamlessly, and has handled all of the submissions that have been thrown at it. I'm very happy with how it works, especially as it frees me up to move the hosting where I want - the Netlify lock-in is no more!

If you have any questions, or just want to test out whether it works, please do feel free to use the contact form to send me a message!