A contact form backend that keeps messages in the EU
Paste this into any page and replace YOUR_FORM_ID with the ID from your dashboard. Messages arrive in your inbox, a copy is stored on servers in Sweden, and bots are filtered out before either happens.
<form action="https://forms.formward.eu/f/YOUR_FORM_ID" method="POST">
<label>Name <input type="text" name="name" required></label>
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<label>
<input type="checkbox" name="_consent">
I agree that you store my message to answer it.
</label>
<!-- Honeypot: off-screen, must stay empty -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px;opacity:0">
<button type="submit">Send</button>
</form>The checkbox is optional. Switch on consent capture in the form settings and Formward records the wording you configured and the time the box was ticked. It works on every plan, Free included.
Three ways to put it on your site
Your own HTML. The snippet above, styled however you like. This is the usual choice when you control the markup, whether that is a hand-written page, a theme template or a framework component.
A popup button. One script tag adds a floating button that opens a contact form. Useful on site builders where editing form markup is awkward.
<script src="https://formward.eu/formbutton.js"
data-form-id="YOUR_FORM_ID"
data-button-text="Contact us"></script>A hosted form link. Formward can serve the form itself at formward.eu/s/your-slug, which you link to from anywhere. See online forms for how that works.
All three post to the same form, so the messages end up in one place.
Spam protection without reCAPTCHA
Contact forms attract more bots than any other form. The checks run in this order:
- Allowed origins. List your domain and posts from anywhere else are refused.
- Rate limit. Repeated posts from one hashed IP are refused with a 429. You can set the limit per form.
- Honeypot. The hidden
_gotchafield. Bots that fill it are told the send worked; you never see the message. - Disposable addresses. A per-form switch rejects submissions from throwaway email domains.
- Turnstile. Optional on every plan. Add the widget and your secret key, and Formward verifies the token on its side.
- AI spam score. On Professional and Business, submissions that pass the rest get a score, and likely spam is held back from your inbox but kept for review.
Consent capture
Name the checkbox _consent and turn on consent capture in the form settings. Formward stores the consent text from your settings, not whatever the page claimed, along with the time. If you mark consent as required, an unticked box is rejected with a 422 before anything is stored.
Many contact forms do not need consent at all, because answering a message the visitor sent is usually covered by another lawful basis. The consent guide goes through when it applies.
Answering messages
A field named email becomes the Reply-To header on the notification, so you can answer straight from your mail client. If your address field has another name, rename it to email or add a _replyto field. Every message is also kept in the dashboard, so a deleted email does not mean a lost enquiry. From Personal up you can search and export them.
Where the data lives
Formward stores submissions, file attachments and backups in Sweden, on servers run by Hostup AB. Notification email goes out through Mailjet from an EU data centre. Submitter IP addresses are replaced with a keyed HMAC-SHA-256 hash on receipt, so the raw address is never written to disk. On Professional and Business, AI features run on Mistral AI in France and can be switched off per form.
Data leaves the EU only where you send it: a notification inbox hosted outside the EU, a webhook URL, a Slack channel or a Google Sheet. Cloudflare Turnstile is a US service, and it is off unless you switch it on for a form. The details are in the sub-processor register, the data residency page and the published DPA. For the wider picture, see GDPR form backend.
Questions
- Do I need JavaScript?
- No. The form above is plain HTML and submits with a normal POST. JavaScript only comes in if you want to stay on the page with fetch(), or if you use the Formbutton popup.
- Is a CAPTCHA required?
- No. The honeypot, rate limit and disposable-address block run without any widget. Cloudflare Turnstile is there if a form still gets through, and it is off by default.
- Can several people receive the messages?
- Yes, from the Personal plan. Each extra recipient confirms their address before they start receiving submissions.
- How long are messages kept?
- On Free, 30 days. On paid plans you pick a period between 1 and 3,650 days per form, or keep messages until you delete them.
- Does it work with React, Next.js or Vue?
- Yes. The endpoint does not care what rendered the form. The framework pages show the same form as a component with fetch().
Put a contact form live today
Start free with 50 submissions a month. No card needed.