You can have every form submission from your website land as a message in a Discord channel, and the whole setup takes about five minutes. There are two ways to get there: write a small serverless function that translates form posts into Discord's webhook format, or point your form at an endpoint that already speaks Discord. This guide walks through both so you can pick the one that fits.
Why Discord, not your inbox
If you build side projects, Discord is probably already open on your machine. Email is the default destination for contact forms, and it's also where messages go to die. A form notification lands between newsletters, receipts, and GitHub noise, in an inbox you check twice a day. A ping in a #contact channel gets seen in minutes. And a channel is shared by default: a collaborator can spot a message and answer it without you forwarding anything.
Step 1: Create a Discord webhook
A webhook is a URL that lets anything post messages into one specific channel of your server. Creating one takes a minute:
- Open your Discord server, click the server name in the top-left corner, and choose Server Settings.
- In the left sidebar, open Integrations, then Webhooks.
- Click New Webhook. Discord creates one with a random name. Rename it to something like "Contact form" and pick the channel it should post to (a dedicated
#contactchannel works well). - Click Copy Webhook URL.
The URL looks like https://discord.com/api/webhooks/1234…/AbCd…. Treat it like a password: anyone who has it can post into that channel.
Step 2, the DIY route: glue code between form and webhook
Here's the catch: a Discord webhook is not a form endpoint. It expects a POST with Content-Type: application/json and a body in Discord's own shape, at minimum {"content": "your message"}. An HTML form submits URL-encoded or multipart data, so pointing your form's action straight at the webhook URL fails. It would also publish your webhook URL in your page source, where anyone could grab it and flood your channel.
So the classic setup puts a serverless function between the two. Your form posts to the function; the function reformats the fields and forwards them to Discord:
// api/contact.js: a serverless function (Vercel, Netlify, etc.)
export default async function handler(req, res) {
const { name, email, message } = req.body;
if (!email || !message) {
return res.status(400).json({ error: "email and message are required" });
}
await fetch(process.env.DISCORD_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: `**${name || "Someone"}** <${email}>\n${message.slice(0, 1800)}`,
}),
});
res.redirect(303, "/thanks.html");
}This works, and it's a fine weekend project. But you now own everything around it: validation, spam filtering once bots find the endpoint, Discord's webhook rate limits, and error handling. Discord messages cap at 2,000 characters, hence the slice: longer messages get cut. And the biggest limitation is that Discord becomes your only copy. If the fetch fails, or a message scrolls past while you're away for a weekend, the submission is gone. There's no archive, no reply button, no "did I answer this?" state.
Step 3, the no-glue route: a form endpoint that speaks Discord
With mailcontact, the glue code disappears. Your form posts to your project's endpoint (plain HTML, no JavaScript required):
<form action="https://mailcontact.app/api/forms/your-form-id" method="POST">
<input type="text" name="name" placeholder="Your name" />
<input type="email" name="email" placeholder="you@email.com" required />
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send</button>
</form>Then, in your project's notification settings, add Discord as a channel and paste the webhook URL you copied in step 1. That's the whole setup: every submission lands in your project's inbox and pings the channel. The notifications docs cover the details.
A few things you get for free with this route. Your webhook URL stays server-side instead of sitting in your page source. Validation and rate limiting are handled for you. The inbox keeps the full message even when it's longer than a Discord message. And because each project also gets a real email address like yourproject@mailcontact.app, messages sent by email ping the same channel: form and email in one place. Discord notifications are included on the free plan.
One thing to know while mailcontact is in beta: the endpoint responds with JSON rather than redirecting, so to keep visitors on your page, submit the same fields with a few lines of fetch. The form endpoint docs have a copy-paste example. A configurable redirect for native form posts is coming with the launch.
The markup is the same whatever you build with, but the place you paste it isn't. There are stack-specific versions of this setup for Next.js contact forms, Astro contact forms, and Cloudflare Pages, which has no form handling of its own at all.
What a good ping contains
Whichever route you take, the Discord message should let you triage from your phone without opening anything else. That means four things:
- Name: who is writing, so "reply to Ada" is a thought you can actually hold.
- Email: how to reply. This is the field DIY versions most often forget to include, which turns a ping into a dead end.
- Message: the first few lines are enough to decide between "now", "tonight", and "ignore".
- Source: which form or address it came through, so a ping is unambiguous once you run more than one project.
A good ping reads something like this:
acme-site: new message
Ada Lovelace <ada@example.com>
"Hey, the checkout button 404s on mobile. Happy to
send a screen recording if that helps."
via: contact formKeep the ping skimmable and keep the full record somewhere permanent. That split is exactly why a channel plus an inbox beats either one alone. If you want the five-minute version: create a project, paste the form snippet, paste the webhook URL (the beta is invite-only for now; the waitlist is how you get in). And if your site is static with no backend at all, the companion guide on adding a contact form to a static site covers that half.