How to add a contact form to a static website without a backend.
A static website is a fast, cheap, and reliable way to launch a landing page, promo page, documentation, or product site. But it has a typical problem: how to accept submissions if there is no backend?
For example, you have a site on Cloudflare Pages, GitHub Pages, Netlify, Vercel, S3/Selectel Object Storage, or any other static hosting. HTML, CSS, and JavaScript work great, but the feedback form itself cannot send data to Telegram, Email, or CRM.
In this article, we will discuss how to add a form to a static site without a backend, what options are available, and what to pay attention to so that submissions do not get lost.

Problem
A regular HTML form looks simple:
<form>
<input name="name" placeholder="Your name" />
<input name="email" placeholder="Email" />
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send</button>
</form>
But such a form does nothing useful until it has a handler.
To ensure that the submission actually reaches you, you need to:
- accept the form data;
- verify that the request came from an allowed site;
- protect against spam;
- save the submission;
- send a notification to Telegram, Email, or Webhook;
- handle delivery errors;
- provide the user with a clear result: success or failure.
If there is no backend, all of this has to be solved in another way.
Bad option: mailto:
Sometimes a form is attempted to be replaced with a link to an email:
<a href="mailto:sales@example.com">Write to us</a>
Or they create a form that opens the user's email client.
This is a bad option for lead generation.
Cons:
- the user may not have a configured email client;
- the submission is not saved in the system;
- there is no analytics;
- fields cannot be properly validated;
- cannot protect against spam;
- cannot send data directly to Telegram, CRM, or webhook;
- the user may simply close the email window and send nothing.
mailto: is suitable only as a backup contact, but not as the main submission form.
Option 1: write your own backend
The classic solution is to create your own endpoint:
POST /api/contact
It accepts the form data and sends it further.
Pros:
- full control;
- can implement any business logic;
- can integrate with CRM, Telegram, Email, analytics.
Cons:
- needs a backend developer;
- needs to host the API somewhere;
- needs to maintain security;
- needs to monitor notification delivery;
- needs to implement anti-spam;
- needs to log errors;
- needs to maintain infrastructure.
This is fine for a large product. For a landing page, promo page, or MVP, it is often excessive.
Option 2: serverless function
You can create a form handler on Cloudflare Workers, Vercel Functions, Netlify Functions, or AWS Lambda.
Approximately like this:
export default {
async fetch(request) {
const data = await request.json()
// validate data
// send telegram notification
// send email
// return response
return Response.json({ ok: true })
}
}
Pros:
- no need for a full server;
- convenient for simple scenarios;
- can be quickly launched.
Cons:
- code still needs to be written and maintained;
- secrets need to be stored securely;
- need to handle retry delivery yourself;
- need to implement spam protection yourself;
- need to maintain webhook/email/telegram logic yourself;
- with multiple sites, code duplication begins.
Serverless is a good option for developers, but not always the best option for businesses, agencies, and teams that need to quickly launch many sites.
Option 3: form backend / lead capture service
A form backend is an external service that accepts POST requests from the form and delivers the submission to the desired channels.
The general principle:
Static Website → Form POST → Lead Capture Service → Telegram / Email / Webhook
On the site, only the HTML/JS code of the form remains. All backend logic is in the external service.
This approach is convenient if you need to:
- quickly connect a form;
- not write a backend;
- receive submissions in Telegram;
- send copies to Email;
- pass data to CRM via webhook;
- protect the form from spam;
- see the history of submissions;
- not lose submissions during temporary delivery errors.

Example of form integration
Below is an example of a simple form that sends a submission via JavaScript.
<form id="lead-form">
<input name="name" placeholder="Your name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send</button>
</form>
<script>
const form = document.querySelector('#lead-form')
form.addEventListener('submit', async (event) => {
event.preventDefault()
const formData = new FormData(form)
const payload = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message')
}
const response = await fetch('https://api.example.com/api/c1/sites/YOUR_SITE_KEY/submissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
if (!response.ok) {
alert('Failed to send submission. Please try again.')
return
}
form.reset()
alert('Submission sent.')
})
</script>
YOUR_SITE_KEY — the public key of the site. It is needed for the service to understand which site the submission belongs to.
What should be in a good form backend
Simply accepting a POST request is not enough. For a production scenario, additional things are important.
1. Domain verification
The service must understand that submissions are indeed coming from your site.
For this, you can use domain verification via a DNS TXT record.
Example:
subscribe-verification=abc123
This reduces the risk of someone connecting a foreign domain or starting to send junk on behalf of your site.
2. Captcha
A public form quickly starts receiving spam.
For simple forms, often a built-in math captcha is sufficient:
7 + 4 = ?
The user solves a simple problem, and the service checks the answer on the backend side.
Important:
- the challenge should have a TTL;
- one challenge cannot be reused;
- the verification should occur on the server;
- the answer cannot be trusted solely to frontend code.
3. Rate limiting
Even with captcha, it is worth limiting the frequency of submissions.
For example:
no more than 10 submissions per minute from one IP
Rate limiting protects against:
- bots;
- accidental resubmissions;
- simple flood spam;
- overloading notification channels.
4. Delivery to multiple channels
A good scenario is to send the submission to several places at once:
Telegram + Email + Webhook
For example:
- Telegram — to quickly see the submission;
- Email — to keep a copy;
- Webhook — to send data to CRM or an internal system.
5. Retry on errors
Webhook or Email may temporarily not work.
For example:
- CRM returned
500; - SMTP server temporarily unavailable;
- Telegram API returned an error;
- network timed out.
If the service simply tries once and forgets the submission — this is poor reliability.
A retry mechanism is needed:
1st attempt → error
2nd attempt after 30 seconds
3rd attempt after 2 minutes
4th attempt after 10 minutes
This way, submissions are not lost due to temporary failures.

When to use a ready-made form backend
A ready-made service is especially useful if:
- the site is static;
- you don't want to write a backend for just one form;
- you need to quickly launch a landing page;
- you need to receive submissions in Telegram;
- you need to connect a webhook to CRM;
- there are many sites;
- submissions cannot be lost;
- simple spam protection is needed.
However, if the form is part of a complex product with a personal account, payments, custom business logic, and internal processes — it is better to create your own backend.
Conclusion
Adding a feedback form to a static site without a backend can be done in several ways:
mailto:— the simplest but unreliable option;- your own backend — flexible but expensive to maintain;
- serverless function — convenient for developers but requires code;
- form backend — a quick way to accept submissions without your own backend infrastructure.
For landing pages, static sites, promo pages, and MVPs, an external lead capture service is the best fit: the site sends the form to the API, and the service delivers the submission to Telegram, Email, or Webhook.
This way, you don't write a backend for just one form and still get reliable delivery, anti-spam, submission history, and integration control.
CTA
Want to accept submissions from your site without a backend?
Connect Form Hook: add the SDK, verify the domain, and receive leads in Telegram, Email, or Webhook.