Send HTML form submissions to Slack — no backend required

The standard way to get HTML form submissions into Slack is wrong. It’s either (1) a Zapier zap that costs $20/month and runs on a five-minute lag, (2) a serverless function you wrote, deployed, and now have to maintain forever, or (3) some “free” form-handler service that requires a signup and then locks Slack delivery behind a paid plan anyway.

All three break the same way: the form, which is two lines of HTML, ends up with a Rube Goldberg machine behind it that you didn’t want and don’t enjoy.

Here’s the version with no machine.

The five lines

curl -X POST https://api.gopigeon.dev/new -d '[email protected]'

That returns a JSON blob with an endpoint URL. Drop that URL into your form’s action:

<form action="https://api.gopigeon.dev/f/f_abc123" method="POST">
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>

Submissions hit the email you specified. Slack hasn’t entered the picture yet — that’s the next step.

Adding the Slack fan-out

When the first real submission arrives at your inbox, it carries a one-click link to claim the endpoint. Click it. You’re now the authenticated owner.

In the dashboard, add a Slack destination — paste your channel’s incoming webhook URL once, save. From the next submission forward, every form fill simultaneously emails you and posts to Slack. Same submission, two destinations, no extra code. Discord works the same way (paste a Discord webhook URL); generic webhooks too (paste your own endpoint).

The whole flow is documented at destinations — fan-out is a Pro feature, but Pro is $12/month and includes 10,000 submissions, 90-day retention, and unlimited destinations.

Why this is shaped right

Two things people get wrong when they roll their own Slack-for-forms.

They forget about retries. Slack’s webhook endpoint goes down sometimes. Or rate-limits. If your serverless handler posts directly to Slack and the post fails, the submission is gone unless you wrote durable retry logic, which most homegrown handlers don’t. gopigeon retries the Slack post with backoff in the background and surfaces failures in the dashboard. Your form never sees the failure; your serverless handler doesn’t need to exist.

They forget about spam. A raw form-to-Slack handler will happily forward every honeypot-tripping bot submission to your team channel. Within a week the channel is unreadable. gopigeon runs spam filtering before fan-out, and exposes _gotcha as the canonical honeypot field — any non-empty value silently drops the submission and never fires the Slack post.

You could re-implement both of these yourself. People do. Then they spend a Saturday afternoon debugging why one user’s submission keeps getting rejected because the spam classifier didn’t like the word “test” in the message body. There’s a better use of a Saturday afternoon.

Multi-destination is the point

The reason this is a separate post — instead of just a footnote in the docs — is that Slack-for-forms is usually where people first realize fan-out matters. The contact form arrives in your inbox and in your team’s #leads channel and in your CRM via webhook and in your Discord community for moderation review. One submission, one HTTP POST from the browser, four destinations on the receiving end. That’s the shape of an actual lead-gen pipeline, and trying to compose it yourself out of three different SaaS subscriptions is the part that always breaks.

If you’re already running a queue worker for your agent jobs (see the task queue post), webhook fan-out can drop straight into the same consumer instead of a per-destination integration.

Try it

The curl works without an account. Set up a form, claim it on the first real submission, add a Slack destination from the dashboard. Total time from zero to Slack notification: about three minutes.

curl -X POST https://api.gopigeon.dev/new -d '[email protected]'

Destinations reference · HTML form quickstart · Curl reference · Pricing

← Back to writing