Skip to main content

Quickstart

From signup to your first delivered submission in under 5 minutes.

1. Sign up and create a form

Sign in with a magic link. From the dashboard, click New form, give it a name like contact, and copy the endpoint URL.

2. Drop the endpoint into your HTML

Replace YOUR-FORM-ID with the ID from the dashboard. Any field names you use here will appear in the destination delivery.

html
<form
  action="https://routepost.dev/api/f/YOUR-FORM-ID"
  method="POST"
>
  <input name="name"    placeholder="Your name"    required />
  <input name="email"   placeholder="you@example.com" type="email" required />
  <textarea name="message" placeholder="What's up?" required></textarea>

  <!-- Honeypot — must stay empty. Bots fill it; humans don't. -->
  <input type="text" name="website" style="display:none" tabindex="-1" autocomplete="off" />

  <button type="submit">Send</button>
</form>

3. Add a destination

Open your form in the dashboard, scroll to Destinations, and add at least one. Email + Slack + webhook can all coexist — every destination receives every submission in parallel.

4. Test it

Send a request from the terminal to verify everything before publishing the form:

bash
curl -X POST https://routepost.dev/api/f/YOUR-FORM-ID \
  -d "name=Carlos&email=carlos@example.com&message=Hello"

You should get {"success":true,"delivered":1,"failed":0} and the submission appears in the dashboard within a second.

5. (Optional) Redirect after submit

Set a redirect_url on the form to send users to a thank-you page after submission. Without it, the API returns JSON.

Destinations

Where each submission gets sent. Three types are supported; add as many of each as you need.

email

Email

Each submission delivered as a formatted email. Best for low-volume forms — contact, support, sales leads.

What you see in the dashboard:

Add

No JSON, no API — just a dropdown for the type and a text field for the address.

Sample email body the recipient sees:

New submission: contact

nameJane Doe
emailjane@acme.com
messageTell me more about Pro
slack

Slack

Post each submission to a Slack channel via incoming webhook. Best for team notifications and high-touch workflows.

First, get a Slack webhook URL:

  1. In Slack: your-workspaceApps → search Incoming Webhooks → Add to Slack
  2. Pick a channel, click Add Incoming Webhooks integration
  3. Copy the Webhook URL (starts with https://hooks.slack.com/services/…)

Then paste it in the dashboard:

What you see in the dashboard:

Add

No JSON, no API — just a dropdown for the type and a text field for the address.

webhook

Custom webhook

POSTs the submission as JSON to any URL. Use for Zapier, Make.com, your own backend, or anywhere a webhook can land.

What you see in the dashboard:

Add

No JSON, no API — just a dropdown for the type and a text field for the address.

Payload your endpoint will receive:

json
{
  "form": "contact",
  "received_at": "2026-06-18T18:00:00.000Z",
  "data": {
    "name": "Jane Doe",
    "email": "jane@acme.com",
    "message": "Tell me more about Pro"
  }
}

Your endpoint should respond with a 2xx within a reasonable timeout. Non-2xx responses are recorded as failures in the submission delivery report.

Delivery modes

Different destinations deliver on different schedules. Pick the right destination type for the latency you need.

Real-time — Slack and webhook

Slack and webhook destinations fire immediately on every submission, on every plan. The submission endpoint posts to them inline; you'll see the message in Slack (or the request hit your webhook) within a couple of seconds of the form submit.

Use these when you need low-latency notification or when you're feeding submissions into a downstream system (Inngest, Zapier, n8n, EventBridge, your own queue).

Digest — email

Email destinations are always digest-mode. Submissions accumulate, and the digest email goes out when either cadence clause fires — whichever happens first:

  • Submissions cadence — N new submissions since the last digest.
  • Time cadence — M minutes since the last digest.

Each digest is capped at 100 submissions per email; backlogs roll into the next run. The cron evaluates due destinations every 5 minutes.

Per-plan cadence floors

Cadence values are configurable per destination, but no plan can go below its floor. These floors exist so email stays a summary medium — for real-time delivery use a Slack or webhook destination.

PlanSubmissions floorTime floor
Freeevery 50 submissionsdaily (1440 min)
Starterevery 25 submissionshourly (60 min)
Proevery 10 submissionsevery 10 min

Need real-time? Add a Slack or webhook destination — they fire on every submission, no cadence floor applies.

Destination verification

Why RoutePost requires you to confirm a destination before submissions start flowing — and what happens if you don't.

Without verification, anyone could sign up for RoutePost, point a destination at someone-elses-address@example.com, and use the public form endpoint to spam them under our domain. To shut that vector down, every destination has to prove it belongs to the form-owner before it can deliver.

By destination type

  • Email. Adding an email destination sends a one-click verification link to that address. Submissions to the form are rejected until the recipient clicks Confirm destination. The link expires 24 hours after sending. You can resend from the dashboard up to per 24h, no more than once per minute.
  • Slack. Adding a Slack webhook fires a test message to the channel naming the RoutePost account that connected it, plus a link the channel members can click to detach. The destination is treated as verified on add (Slack webhook URLs are workspace-controlled secrets — you couldn't have one unless someone in your workspace created it).
  • Webhook. Same model as Slack: adding the destination POSTs a one-time payload of type routepost.verification to the URL with an owner_email and a disconnect_url. The endpoint can either ignore it or surface the disconnect link to a human.

What submitters see when a form has no verified destinations

If every destination on the form is pending verification or disabled by its recipient, the public endpoint rejects the submission outright with HTTP 422:

json
{
  "error": "no_verified_destinations",
  "message": "This form has no verified destinations. The form owner must complete destination verification before submissions are accepted."
}

The submission is not stored — it isn't queued for later either. Your form's frontend should surface the message so the form-filler doesn't assume their data was accepted.

Editing a destination after the fact

  • Editing only the label or routing rules keeps the destination verified.
  • Editing an email destination to point at a different address resets verification and sends a fresh confirmation link to the new address. As a courtesy, the old address gets a notification that the destination was moved.
  • Editing a Slack or webhook URL re-fires the test-ping with a fresh disconnect link. The destination stays in the verified state (same verified-on-add reasoning as creation).

If a recipient disconnects

When someone clicks the disconnect link in a Slack/webhook test-ping (or in the post-confirmation page for an email destination), the destination is marked Disabled by recipient in the dashboard and stops receiving submissions immediately. The form-owner can delete it and create a new one if needed. Disconnect links remain valid for 30 days from when they were issued.

Routing rules

Conditionally deliver a submission to a destination only when fields in the submission match a rule. Useful for routing high-value leads to sales-Slack and everything else to a support email.

On every destination you can add one or more rules. All rules must match (logical AND) for that destination to receive the submission. Empty rules = unconditional (the default).

Example: route enterprise leads to sales-Slack

Suppose your HTML form has a dropdown field interest with values starter, team, and enterprise. You can:

  • Add a slack destination pointing at #sales, with a rule: interest equals enterprise
  • Add a second email destination pointing at support@yourdomain.com, with no rules (everything else lands there)

Now every form submission is automatically routed — sales gets enterprise leads in Slack instantly; support handles the rest by email. No Zapier in the middle.

Supported operators

OperatorMatches when…
equalsfield value matches the configured string exactly
not_equalsfield value is NOT the configured string
containsfield value contains the configured substring
not_containsfield value does not contain the configured substring
starts_withfield value starts with the configured string
is_emptyfield is missing or empty
is_not_emptyfield exists with any non-empty value
regexfield value matches the configured regular expression

Rules evaluate on the server immediately after the submission lands. If a destination's rules don't match, that destination is simply skipped (no delivery, no error). The submission still appears in your dashboard inbox.

Autoresponder

Send an automatic thank-you reply to the submitter as soon as their form lands. Off by default — toggle per form.

Enable it on a form

Open the form in your dashboard → Form settings → toggle Auto-reply to submitter. Configure which field holds the email, the subject, body, and optional Reply-To.

Template variables

Use {{ field_name }}in either subject or body — it's replaced with the value of that submission field. {{ form_name }}substitutes the form's display name.

text
Subject: Hi {{ name }}, we got your message

Body:
Hi {{ name }},

Thanks for reaching out via {{ form_name }}. We received your message:

  "{{ message }}"

Someone on our team will reply within 24 hours.

— The {{ form_name }} team

Deliverability — read this

  • Auto-replies go out via your configured Resend account. The From address is whatever you set as RESEND_FROM_EMAIL on the server.
  • For replies to land in real inboxes (not spam), the sender domain must be verified in Resend — same as transactional email.
  • Set a Reply-Toto a real address you monitor; otherwise submitter replies go to the auto-reply's From address (often a noreply mailbox).
  • Honeypot-flagged submissions never trigger the autoresponder.

If the autoresponder skips silently

A few reasons it might not fire:

  • The configured field is missing or empty in the submission
  • The recipient value isn't a valid email address
  • Resend isn't configured on the server (missing API key)
  • Your plan isn't in FEATURE_AUTORESPONDER_PLANS
  • The instance has FEATURE_AUTORESPONDER=false

File uploads

Accept file attachments alongside form fields. Files are stored, URLs flow through to every destination.

File uploads are off by default. To enable them on a form, open it in the dashboard → Form settings → toggle Accept file uploads.

HTML form with file inputs

html
<form
  action="https://routepost.dev/api/f/YOUR-FORM-ID"
  method="POST"
  enctype="multipart/form-data"
>
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>

  <!-- Single file -->
  <input name="attachment" type="file" />

  <!-- Multiple files -->
  <input name="screenshots" type="file" multiple />

  <button type="submit">Send</button>
</form>

Critical: include enctype="multipart/form-data" on the <form> tag. Without it, browsers send only the filenames, not the actual bytes.

Limits

  • Max 25 MB per file
  • Max 50 MB total per submission
  • Any file type is accepted (no MIME filter at the server)
  • Files are stored in Supabase Storage with public URLs
  • If file uploads are disabled on the form, files are silently dropped (fields still process)

What destinations see

Each destination receives URLs to the uploaded files alongside the form data.

Email destination: file links appear below the data table in the email body.

Slack destination: an Attachments section with clickable links is appended to the message.

Webhook payload now includes:

json
{
  "form": "contact",
  "received_at": "2026-06-18T18:00:00.000Z",
  "data": { "email": "jane@acme.com", "message": "..." },
  "attachments": [
    {
      "name": "screenshot.png",
      "size": 12384,
      "type": "image/png",
      "url": "https://nansd…supabase.co/storage/v1/object/public/submission-files/…"
    }
  ]
}

Dashboard

Where you manage forms, destinations, and inspect submissions.

Forms list

Landing screen after signing in. One card per form, with a quick submission count.

routepost.dev/dashboard
Your forms

Contact form

42 submissions

Newsletter signup

318 submissions

Bug report

11 submissions

Partner inquiry

3 submissions

Form detail

Endpoint URL (with copy button), destinations management, and recent submissions — all on one page.

routepost.dev/dashboard

Endpoint

https://routepost.dev/api/f/abc-123

Destinations

  • emailteam@acme.com
    Remove
  • slackhooks.slack.com/…
    Remove

Recent submissions

Last 100 submissions per form, with IP and timestamp. Click a row to see the full payload.

routepost.dev/dashboard
ReceivedData
2026-06-18 12:34name: Jane · email: jane@acme.com · plan: pro
2026-06-18 12:30name: Bob · email: bob@example.com
2026-06-18 12:18name: Carlos · ref: launch-week
2026-06-18 11:55email: alice@beta.io · question: pricing

Spam protection

A small amount goes a long way. Bots are dumb, your honeypot is free.

Every form has a hidden honeypot_field (defaults to website). Bots fill every input they find; humans don't see hidden fields. When the honeypot arrives non-empty, RoutePost returns 200 and silently discards the submission — no notification, no inbox row, no quota hit.

html
<form action="https://routepost.dev/api/f/YOUR-FORM-ID" method="POST">
  <input name="email" type="email" required />

  <!-- HONEYPOT — keep empty -->
  <input type="text" name="website"
         style="display:none" tabindex="-1" autocomplete="off" />

  <button>Send</button>
</form>

You can rename the honeypot field per form (e.g. company or phone2) so bots that learn one site's convention still miss yours.

CORS

The submission endpoint accepts cross-origin requests from any domain — no whitelist required.

All POST /api/f/[form_id] responses include Access-Control-Allow-Origin: *, so a form hosted anywhere — your marketing site, a static SPA, a customer's portal — can submit without any proxy or server-side relay.

If you're submitting via fetch() from JavaScript, the response is JSON:

javascript
const res = await fetch(
  'https://routepost.dev/api/f/YOUR-FORM-ID',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, message }),
  }
);
const { success, delivered, failed } = await res.json();