One form. Every conversation.
Send a confirmation to your customer, notify your team, and route submissions into the services you already use.
Choose where each message goes
- Open New form or edit an existing form. Build your fields first.
- Under Delivery & confirmations, choose Email, SMS, Slack, Discord, or HTTPS webhook. Add up to 20 rules per form.
- For email or SMS, choose a fixed recipient for team notifications, or a required form field for submitter confirmations.
- Add provider credentials, write your message, and inspect the example preview. Save the form.
- Submit a real test from an allowed origin using an address or number you control.
Use separate rules to send both a team notification and a customer confirmation. Each rule has a When to send setting — new rules start as manual only; enable automatic delivery for the rules that should run after accepted submissions.
Email with your own SMTP server
Choose My own SMTP server. Enter your provider’s SMTP hostname, username, password or app password, and a verified sender email. Use port 587 for STARTTLS or 465 for TLS. Private or local mail servers are not supported, and transport uses encrypted connections with certificate verification.
For a confirmation, add a required Email field and select it as the recipient. Add a subject and plain-text message:
Hi {{name}},
Thank you for contacting {{form.name}}.
We received your message and will reply soon.
Your reference is {{submission.id}}.Service default SMTP uses the service administrator’s configured mail server and works only when the backend has SMTP configured. If delivery fails, check the sender’s verification, provider access rules, and credentials.
SMS with Twilio
- In your Twilio account, get your Account SID and Auth Token, and an SMS-capable sender number.
- Add an SMS rule and enter the SID, token, and sender number.
- Enter a fixed recipient or select a required text field with an international number, e.g.
+14155550123. - Write a short confirmation and save. Test with your own phone number.
Twilio controls account restrictions, allowed destinations, sender registration, delivery, and charges. Trial accounts may require verified recipients. Other SMS providers can be connected through a webhook adapter.
Notify a Slack channel
- In Slack, create an incoming webhook for the target channel (you need permission to add apps).
- Copy the URL, which starts with
https://hooks.slack.com/services/. - Add a Slack rule and paste the URL into the protected webhook field, then write your notification.
- Save and submit a test form.
The URL is a credential: anyone with it can post to the channel. Contact encrypts it and does not show it again after saving. Messages are sent as plain text with link previews and Markdown disabled, so submitted content cannot ping everyone or inject formatting.
Notify a Discord channel
- Create an incoming webhook for a standard text channel in your Discord server’s integration settings.
- Copy its URL and add a Discord rule in Contact.
- Paste the URL into the protected webhook field, then write your notification.
- Save and submit a test form.
The URL is a credential and is encrypted at rest. Messages use plain content with mentions disabled, so submitted text cannot ping everyone. Forum channels and thread routing are not supported.
Connect other services
Add an HTTPS webhook for your server or an automation workflow (n8n, Make, Zapier). Use a public HTTPS endpoint on port 443 without embedded credentials and set a signing secret of at least 24 characters. Contact posts this event, with your rendered message when a template is supplied:
{
"id": "submission-id",
"deliveryId": "delivery-job-id",
"type": "form.submission.created",
"createdAt": "2026-09-30T12:00:00Z",
"form": { "id": "form-id", "name": "Contact us" },
"data": { "name": "Alex", "email": "alex@example.com" },
"message": "Hi Alex, we received your message."
}Your receiver should verify the signature using the exact body bytes and reject stale timestamps:
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody must be the exact request bytes, before JSON parsing.
function validSignature(rawBody, timestamp, signature, secret) {
if (!/^\d+$/.test(timestamp ?? "")) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
if (!/^v1=[a-f0-9]{64}$/.test(signature ?? "")) return false;
const expected = createHmac("sha256", secret)
.update(timestamp + ".").update(rawBody).digest();
return timingSafeEqual(expected, Buffer.from(signature.slice(3), "hex"));
}
// Read X-Forms-Timestamp and X-Forms-Signature headers.
// After verification, deduplicate using event.deliveryId before processing.Return HTTP 2xx after accepting the event. Redirects are not followed. Deduplicate on deliveryId, which stays stable across retries, because delivery retries can repeat an event.
Personalize the message
Use field keys inside double braces, for example {{name}}, {{email}}, or {{message}}. Metadata tokens are {{form.name}}, {{submission.id}}, and {{submission.createdAt}}.
Templates are plain text with field substitution: no HTML, scripts, loops, or nested expressions. Missing optional fields become empty text, and unknown keys are rejected when saving. Length caps: email subject 200, SMS 1,600, Discord 2,000, Slack 4,000 characters. Rendered content above these limits is truncated.
Delivery and troubleshooting
202 means the submission was stored and its delivery rules were queued — not that an email or SMS has arrived. Spam is never sent.Failures retry with increasing delays, up to eight attempts. SMS and email acceptance by a provider does not guarantee final delivery. When editing, leave a saved credential blank to keep it, or enter a new value to replace it. Removing a rule stops it for future submissions; already-queued messages keep the settings they were queued with.
If nothing arrives, confirm the worker is running, inspect its delivery queue and errors, verify credentials and recipient format, and check the provider’s delivery logs. The backend and worker must share the same DATA_ENCRYPTION_KEY; changing it without re-encrypting stored integrations makes them unreadable.
Still stuck? See Troubleshooting & FAQ.