New: Getform is now Forminit. Read the post ->
Back to all posts

How to Send an HTML Form to Email Without a Server in 2026

By Forminit in Guides • Published February 17, 2026

Short answer: Use a form backend API like Forminit. Add a script tag and a few lines of JavaScript to your HTML form. Submissions are stored, email notifications are sent automatically, and you don’t need a server, SMTP credentials, or any backend code.


You have a contact form. You want submissions in your inbox. Sounds simple, but the moment you start looking into it, you’re knee-deep in SMTP configs, serverless functions, or PHP scripts you found on a forum from 2014.

Here’s every real way to get form data to email in 2026, starting from the most primitive and working up to the option that takes the least effort for the most reliability.

Quick comparison

MethodServer required?Email deliverabilityFile uploadsSpam protectionSubmission storage
mailto:NoN/A (opens mail client)NoNoNo
PHP mail()Yes (PHP host)PoorManualDIYNo
NodemailerYes (Node.js)Depends on SMTPDIY (multer)DIYNo
Serverless functionsNo (but needs config)Depends on email APIDIY (S3/R2)DIYDIY (database)
Cloudflare WorkersNo (but needs config)Depends on email APIDIY (R2/KV)DIYNo
Google Apps ScriptNoGmail limitsNoNoDIY (Sheets)
Form backend APINoBuilt-inBuilt-inBuilt-inBuilt-in

1. Can you use mailto: to send form data?

The oldest approach. Set your form’s action to a mailto: link and let the browser handle it.

<form action="mailto:you@example.com" method="POST" enctype="text/plain">
  <input type="text" name="Name" placeholder="Your name" />
  <input type="email" name="Email" placeholder="Your email" />
  <textarea name="Message" placeholder="Your message"></textarea>
  <button type="submit">Send</button>
</form>

Clicking submit opens the user’s default email client with the form data in the body. The user then has to click send manually.

Cons:

  • Doesn’t actually send anything. It opens a mail client. If the user doesn’t have one configured (common on phones, Chromebooks, and most machines in 2026), nothing happens.
  • Most users will close the window, confused about why their email app just opened.
  • Form data arrives as unformatted name=value text.
  • Your email address is exposed in the page source. Scrapers will harvest it.
  • No spam protection, no validation, no submission history.
  • Broken in most webview and in-app browsers.

This was unreliable in 2005. It’s still unreliable now. Don’t use it for anything real.

2. Can you use PHP mail() to send form submissions?

If your hosting supports PHP (shared hosting, VPS, or any LAMP stack), you can process the form server-side with a short PHP script.

<!-- contact.html -->
<form action="send.php" method="POST">
  <input type="text" name="name" placeholder="Your name" required />
  <input type="email" name="email" placeholder="Your email" required />
  <textarea name="message" placeholder="Your message" required></textarea>
  <button type="submit">Send</button>
</form>
<?php
// send.php
$name = htmlspecialchars($_POST['name']);
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
$message = htmlspecialchars($_POST['message']);

if (!$email) {
    die('Invalid email address.');
}

$to = 'you@example.com';
$subject = 'New Contact Form Submission';
$body = "Name: $name\nEmail: $email\nMessage:\n$message";
$headers = "From: $email\r\nReply-To: $email";

if (mail($to, $subject, $body, $headers)) {
    echo 'Message sent.';
} else {
    echo 'Something went wrong.';
}
?>

Cons:

  • Requires a PHP-capable server. If you’re on a static host (Netlify, Vercel, GitHub Pages, Cloudflare Pages), this doesn’t work.
  • The mail() function uses the server’s local mail transfer agent (sendmail/postfix). Most shared hosts have poorly configured mail servers, so your emails will frequently land in spam or never arrive at all.
  • No authentication (SPF, DKIM, DMARC) unless you configure the server yourself. Gmail and Outlook will reject or junk unauthenticated mail.
  • No built-in spam protection. You’ll get hammered by bots unless you add CAPTCHA or honeypot logic yourself.
  • No submission storage. If an email fails silently (which mail() does regularly), the data is lost.
  • The From header spoofs the sender’s email, which modern mail providers increasingly reject.
  • Input sanitization is on you. The example above covers basics, but real production code needs more.
  • PHP hosting is becoming less common as the industry moves toward static sites and edge deployments.

PHP mail() works on traditional hosting, but the deliverability problems make it unreliable for anything important. If you go this route, at least use a library like PHPMailer with a real SMTP provider.

3. Can you use Node.js with Nodemailer?

If you’re running a Node.js server (Express, Fastify, etc.), Nodemailer lets you send email through any SMTP provider.

import express from 'express';
import nodemailer from 'nodemailer';

const app = express();
app.use(express.urlencoded({ extended: true }));

const transporter = nodemailer.createTransport({
  host: 'smtp.gmail.com',
  port: 587,
  secure: false,
  auth: {
    user: process.env.GMAIL_USER,
    pass: process.env.GMAIL_APP_PASSWORD,
  },
});

app.post('/send', async (req, res) => {
  const { name, email, message } = req.body;

  try {
    await transporter.sendMail({
      from: process.env.GMAIL_USER,
      replyTo: email,
      to: 'you@example.com',
      subject: `Contact form: ${name}`,
      text: `Name: ${name}\nEmail: ${email}\nMessage:\n${message}`,
    });

    res.send('Message sent.');
  } catch (err) {
    console.error(err);
    res.status(500).send('Failed to send.');
  }
});

app.listen(3000);

Cons:

  • You need a running Node.js server. That means hosting, deployment, uptime monitoring, and maintenance.
  • Requires SMTP credentials from an email provider. Gmail works for testing but has sending limits (500/day) and requires app-specific passwords. For production, you need a transactional email service (Resend, SendGrid, Postmark) with its own billing.
  • No submission storage. Email is the only record. If sending fails, the data is gone unless you add a database.
  • No file upload handling unless you add middleware (multer) and figure out where to store files.
  • No spam protection out of the box. You need to add rate limiting, CAPTCHA, and honeypot yourself.
  • Input validation and sanitization is entirely your responsibility.
  • Gmail’s SMTP will stop working if Google changes their app password policy or tightens security requirements (this has happened before).
  • You’re now maintaining a server for what is essentially a contact form.

Nodemailer is solid software, but using it means you’re building and running a backend. That’s a lot of infrastructure for a contact form.

4. Can you use serverless functions (Vercel, Netlify, AWS Lambda)?

Serverless functions let you run backend code without managing a server. You write a function, deploy it alongside your static site, and it handles form submissions.

// Vercel: api/contact.js
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(request) {
  const { name, email, message } = await request.json();

  try {
    await resend.emails.send({
      from: 'forms@yourdomain.com',
      to: 'you@example.com',
      subject: `Contact form: ${name}`,
      text: `Name: ${name}\nEmail: ${email}\nMessage:\n${message}`,
    });

    return Response.json({ success: true });
  } catch (err) {
    return Response.json({ error: 'Failed to send' }, { status: 500 });
  }
}

Client-side:

<form id="contact-form">
  <input type="text" name="name" placeholder="Your name" required />
  <input type="email" name="email" placeholder="Your email" required />
  <textarea name="message" placeholder="Your message" required></textarea>
  <button type="submit">Send</button>
</form>

<script>
  document.getElementById('contact-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const form = new FormData(e.target);

    const res = await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(Object.fromEntries(form)),
    });

    alert(res.ok ? 'Sent!' : 'Failed.');
  });
</script>

Cons:

  • You’re stitching together multiple services: a hosting platform for the function, an email API (Resend, SendGrid, Postmark) with its own account and billing, and possibly a database if you want to store submissions.
  • Each service has its own free tier limits, rate limits, and failure modes. If any one of them goes down or changes pricing, your form breaks.
  • No submission dashboard. Your inbox is the only record unless you add a database (Supabase, PlanetScale, Turso, etc.), which adds another dependency.
  • File uploads require a storage service (S3, R2, Cloudflare Workers KV) on top of everything else.
  • Cold starts can add 200-500ms of latency on the first request after idle periods.
  • No built-in spam protection. Rate limiting, CAPTCHA, and honeypot are all DIY.
  • Vendor lock-in. A Vercel serverless function doesn’t port directly to Netlify or AWS without changes.
  • For a contact form, you’re now managing 2-4 different services. That’s a lot of moving parts.

Serverless functions are the right tool for complex backend logic. For a contact form that sends an email, they’re overbuilt.

5. Can you use Cloudflare Workers?

Cloudflare Workers run at the edge, close to your users, with no cold starts. You can pair them with any email API to send mail from a form.

// worker.js
export default {
  async fetch(request, env) {
    if (request.method !== 'POST') {
      return new Response('Method not allowed', { status: 405 });
    }

    const { name, email, message } = await request.json();

    const res = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${env.RESEND_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from: 'forms@yourdomain.com',
        to: 'you@example.com',
        subject: `Contact form: ${name}`,
        text: `Name: ${name}\nEmail: ${email}\nMessage:\n${message}`,
      }),
    });

    if (res.ok) {
      return Response.json({ success: true });
    }

    return Response.json({ error: 'Failed to send' }, { status: 500 });
  },
};

Cons:

  • Still requires an email sending service (Resend, SendGrid, etc.) with its own account and API key.
  • No submission storage. Same problem as every other DIY approach.
  • No file upload handling without additional storage configuration (R2, KV).
  • No dashboard, no spam protection, no validation out of the box.
  • DNS configuration for email authentication (SPF, DKIM, DMARC) is required if you want emails to actually arrive in the inbox.
  • You still need to write and maintain the code, handle errors, sanitize inputs, and set up monitoring.
  • Workers have memory and execution time limits on the free plan. Fine for contact forms, but you need to know the constraints.

Workers are fast and cheap, but you’re still building and maintaining a form backend by hand.

6. Can you use Google Apps Script?

A lesser-known option. Google Apps Script lets you create a web endpoint that receives form data and sends email using your Gmail account, all within Google’s ecosystem.

// Google Apps Script
function doPost(e) {
  const data = JSON.parse(e.postData.contents);

  GmailApp.sendEmail(
    'you@example.com',
    'New Contact Form Submission',
    `Name: ${data.name}\nEmail: ${data.email}\nMessage:\n${data.message}`
  );

  return ContentService
    .createTextOutput(JSON.stringify({ success: true }))
    .setMimeType(ContentService.MimeType.JSON);
}

Deploy as a web app, set access to “Anyone,” and POST to the generated URL from your form.

Cons:

  • Tied to a personal Google account. If the account gets suspended, locked, or you forget which account you used, the form stops working.
  • Gmail sending limits apply: 100 recipients/day for free accounts, 1,500 for Workspace.
  • The generated URL is long and ugly. It changes every time you deploy a new version.
  • No file uploads without significant additional code.
  • No submission storage unless you also write to a Google Sheet, which adds complexity.
  • Latency can be unpredictable. Apps Script isn’t optimized for low-latency responses.
  • No spam protection, no validation, no dashboard.
  • Google can change the Apps Script platform, deprecate features, or modify quotas without notice. They’ve done this before.

It’s free and it works, but it’s held together with duct tape. Fine for personal projects where you’re the only user.

7. What is the easiest way to send form data to email?

Every method above follows the same pattern: write code to receive form data, send an email, and hope it arrives. Storage, validation, spam protection, and file uploads are all separate problems you solve yourself.

A form backend handles all of it as a single service. Forminit is a form backend API that stores submissions, sends email notifications, and gives you a dashboard to manage everything. No server, no SMTP, no glue code.

<form id="contact-form">
  <input type="text" name="fi-sender-fullName" placeholder="Your name" required />
  <input type="email" name="fi-sender-email" placeholder="Your email" required />
  <textarea name="fi-text-message" placeholder="Your message" required></textarea>
  <button type="submit">Send</button>
</form>

<p id="form-result"></p>

<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
  const forminit = new Forminit();

  document.getElementById('contact-form').addEventListener('submit', async (e) => {
    e.preventDefault();

    const formData = new FormData(e.target);
    const { data, error } = await forminit.submit('YOUR_FORM_ID', formData);

    if (error) {
      document.getElementById('form-result').textContent = error.message;
      return;
    }

    document.getElementById('form-result').textContent = 'Message sent!';
    e.target.reset();
  });
</script>

No PHP. No Nodemailer. No serverless functions. No SMTP credentials. No email API accounts. Create a form on forminit.com, set authentication to Public, add the SDK script and a few lines of JavaScript. Done.

What you get out of the box: email notifications on every submission, a dashboard to view/search/export submissions, file uploads up to 25 MB, server-side validation for emails/phones/URLs, spam protection (reCAPTCHA, hCaptcha, honeypot), UTM tracking, webhooks, and a TypeScript SDK with integrations for Next.js and Nuxt.js.

Which method should you choose?

Every DIY method solves one narrow problem (sending an email) while leaving everything else as an exercise for the reader: storage, validation, spam, file uploads, deliverability, monitoring. Each one requires you to maintain code and manage at least one external service.

A form backend replaces all of that with a script tag and an API call. The trade-off is depending on a third-party service. For most contact forms, that’s a trade-off worth making.

If you also need file uploads on your contact form, see How to Add File Uploads to Your Contact Form Without a Server.