AGM Studios Alexander G. Myers

Work · Automation

A contact form that reaches my phone without exposing my servers

I built a workflow on my lab that fetches site enquiries, filters them and sends them to my phone and a log file. It needs no inbound webhook on the lab. I tested it; it goes live with the site.

Context This site's own contact form My role I designed, built and tested the workflow. Tools n8n, Netlify Forms, Nextcloud Talk, draw.io

The problem

A portfolio form is only useful if messages reach me quickly while I travel. The obvious build, a webhook on my own server, would put a door to the lab on the internet. I wanted the speed without the exposure.

The design

The static host stores each submission. A workflow on my lab asks the host for new ones every five minutes. All traffic goes outward, so the lab needs no inbound webhook. The host token lives in n8n’s credential store and stays out of every export.

View diagram full sizeScroll sideways to see all of it
Polltrigger · every 5 minutesFetch submissionsHTTP GET, outbound · static host's forms APIValidate and de-duplicatecode · honeypot, email check, seen-id store, 20 per pollFormat log linecode · one line per leadPost to TalkHTTP POST · private chat room, phone notificationAppend to leads filefile · NAS, visible in NextcloudGenerated from the exported n8n workflow JSON: node names, types and connectionsare the real ones.
The six nodes as they exist in n8n. The source link downloads the workflow with credentials and IDs removed, so it can be imported into any n8n instance.
made with n8n (workflow), draw.io (diagram generated from the exported workflow JSON) last committed 2026-10-29 source file ↓

What each step guards against

StepGuards against
Poll, outbound onlyAn internet-facing endpoint on the lab
Honeypot fieldSimple form bots; filled-in submissions are dropped silently
Email checkJunk that no one can reply to
Seen-ID storeThe same lead arriving twice when a poll overlaps or retries
Length limits and control-character strippingOversized or malformed input reaching the phone or the log
At most 20 per pollA spam burst flooding the chat room
Self-hosted chat for alertsA third-party messenger holding enquirers’ names and emails
Credentials in n8n’s storeTokens in code or exports; the published JSON has none

How it was tested

Before hosting existed, I pointed the workflow at a local copy of the host’s submissions format with three entries (rerun on 29 October 2026 after the alert moved from a third-party messenger to my self-hosted chat): one real-looking lead, one spam entry with the honeypot filled, and one with an invalid email. I ran the schedule every minute for six runs.

RunWhat the execution log recorded
First poll3 submissions fetched; 1 passed validation, was posted to the Talk room (my phone gets the notification) and was appended to the log file
Next five pollsthe same 3 submissions fetched again; 0 passed de-duplication, nothing sent

All six runs finished with status “success”, and the log file holds exactly one line afterwards. The per-run counts are in the test record (sender details omitted). I set the workflow back to five minutes, pointed it at the real form endpoint, and left it inactive until the site goes live.

Known limit: each poll reads the newest 100 submissions. The host’s free plan accepts 100 a month, so a backlog larger than one page between polls cannot happen at this volume. A busier site would need paging through older submissions. The final test after deploy is a real form submission end to end.

What it proves

SecurityI kept the useful part of a webhook without opening the network.
ReliabilityI tested de-duplication and validation before launch, not after the first double message.
HandoverThe workflow exports as one file and imports into any n8n instance.