Instant confirmations
An instant confirmation is an email Mirio sends the person right after they submit your form, from your team: thanks, what happens next, and your booking link if you like. You write it once per form, in the form's Setup → Confirmation, with npx @trymirio/mirio confirmation, or with Draft with AI, and Mirio sends it within seconds of each new response. It's what most teams otherwise write a webhook for.
Turn it on
In Mirio: open the form's Setup → Confirmation. Write the subject and the message (or press Draft with AI), pick who gets the person's replies, check the preview, turn on Send it to each new response with an email address, and save. The preview shows the email with sample values (Ada as the first name) and the real From and Reply-To.
From the CLI (@trymirio/mirio 0.2.2 and later):
npx @trymirio/mirio confirmation demo-request --on # on, starting from a plain template if nothing is written yet
npx @trymirio/mirio confirmation demo-request --subject "Thanks, {{first_name}}" --body-file mirio/confirmation.txt
npx @trymirio/mirio confirmation demo-request # show it: on or off, From, Reply-To, a preview, what stops it
npx @trymirio/mirio confirmation demo-request --off
--body "…" sets the message inline; "" clears the subject or the message. --json prints { slug, confirmation, effective, changed, starter }.
API: GET and PUT /v1/project/forms/{slug}/confirmation with the site's secret key (below).
The confirmation lives in Mirio, not in the schema file, so the team can change it without a deploy.
Merge fields
The subject and message are plain text with these merge fields, filled in for each person:
| Merge field | Becomes |
|---|---|
{{first_name}} |
The person's first name, from a name question. there when there isn't one, so Hi {{first_name}}, reads "Hi there," |
{{form_name}} |
The form's name |
{{workspace_name}} |
Your workspace's name |
{{booking_link}} |
The form's booking link: the one in Setup → Replies (npx @trymirio/mirio replies <slug> --booking-url …), else the schema's success: { type: 'schedule', url } |
Anything else in {{ }} is refused when you save ({{company}} isn't a merge field), and so is an unclosed {{. A confirmation that uses {{booking_link}} can only be turned on when the form has a booking link.
Nothing from the answers goes in, except the first name, and only when it's one word of letters (apostrophes and hyphens allowed, up to 30 characters); otherwise it reads there. Anyone can type any address into a form, so a confirmation that repeated what they wrote would let strangers send your email, with their text, to whoever they like. Keep it to what your team wrote.
Who gets one
A response gets a confirmation when all of these hold:
- it's finished (not an unfinished response) and not a test response;
- it isn't spam, by Mirio's checks or by triage's verdict;
- the site is live (claimed), and the confirmation is on for the form;
- it has an email answer that looks like one address, at a domain that receives email (Mirio checks the domain's mail records).
It's decided at the same moment as your notifications: after triage has read the response (or after triage fails or takes too long), usually within a few seconds of the submit. Then it's sent.
At most once per response, ever. Mirio decides once, the first time it finishes the response, and keeps the decision. A replayed delivery, "Not spam", or the form being registered again never sends one, and a response that was spam at any point (when it came in, by triage, or marked as spam by someone before Mirio got to it) never gets one, even after "Not spam". A confirmation is instant or not at all: if Mirio gets to a response more than 60 minutes after it came in (after an outage, say), it doesn't send one.
Limits, so a form can't be used to flood an inbox or to wear down the address it's sent from:
- one confirmation to the same address from the same form in 24 hours;
- three to the same address in 24 hours from all forms on Mirio together, whoever's they are;
- 50 from one form in 24 hours while confirmations come from Mirio's address, 200 once they come from your own verified sending domain;
- for the workspace, as many a month as its plan has responses (1,000 during the beta).
A confirmation that isn't sent is recorded with the reason, in the response's Activity: Confirmation not sent · this address already got one from this form in the last 24 hours. The reasons: a test response, spam, it came in as spam, more than 60 minutes late, no email address, a domain that doesn't receive email, {{booking_link}} with no booking link, one of the four limits, an earlier confirmation to the same address that bounced or was marked as spam, the form's confirmations being paused, the confirmation turned off before it went out, or the response's email address changed before it went out. If the mail provider refuses one, Activity says so; it isn't tried again.
Who it's from
- From: with a verified sending domain, your domain's address with the workspace's name:
"Acme" <[email protected]>. Without one, the workspace's name at Mirio's address for mail sent as a team:"Acme" <[email protected]>(a subdomain of its own, apart from Mirio's own mail). - Reply-To: the teammate you pick under Replies go to, at their email in Mirio, so the person's answer reaches them. By default, whoever claimed the site, else the workspace's owner. If the teammate you picked leaves the workspace, it falls back to the default.
- To: the response's own email answer, and only that. If the answer changed between Mirio deciding to send and sending, nothing is sent.
It's sent as your text and as an HTML version of the same words in a plain layout (paragraphs, line breaks, clickable links), with your workspace's logo at the top when it has one: no Mirio branding, no footer, no tracking. The logo is the one in Settings → General (the workspace switcher's), loaded from https://trymirio.com/email/logo/…, which anyone can open since email apps load images without signing in. A logo uploaded as WebP, which desktop Outlook can't show, stays out until it's uploaded again (uploads are PNG now). Your workspace's name is in From, so it isn't repeated in the message.
What's recorded
A sent confirmation is an action on the response, like a reply:
- Activity in Mirio: "Confirmation sent to [email protected]: “Thanks, Ada”".
actionson the submission in the API andnpx @trymirio/mirio submissions --json:{ "kind": "confirmed", "via": "auto", "by": null, "userId": null, "emailed": true, "subject": "Thanks, Ada", "at": "…" }.- Webhooks get a
submission.actionevent withaction.kind: "confirmed"(Webhooks), usually a few seconds aftersubmission.completed. Databases get the row again with the confirmation inmirio_actions. Email and Slack destinations don't get it.
Paused after a complaint or bounces
Mirio's mail goes through one provider for every workspace, and the provider stops sending for everyone when too much of it bounces or is marked as spam. So Mirio watches what happens to each confirmation and pauses a form's confirmations when they're clearly causing trouble:
- Someone marks a confirmation as spam. One is enough: a confirmation is mail the person just asked for, so a complaint usually means the form is getting addresses that aren't the sender's own (a bot filling in real people's emails) or reads as something else.
- Hard bounces: 3 or more in 24 hours that are also at least 10% of the confirmations the form sent in those 24 hours, which happens when the form takes made-up addresses (fake signups, bots), not when someone mistypes theirs. Soft bounces (a full inbox, a server that's down for a while) don't count.
While paused, no confirmations go out (Activity says this form's confirmations are paused), and Mirio never sends another confirmation, from any form, to an address that bounced or complained. Setup → Confirmation says why and when, Home shows Confirmations from Demo request are paused, and a bounce or complaint shows on the response it was for: Confirmation to [email protected] · Bounced: 550 5.1.1 no such user. Once you've checked the form (Turnstile or a stricter email question helps against bots), press Resume. The API shows a pause (paused and the first warning below) but can't resume it: a person should look first.
Draft with AI
Draft with AI in Setup → Confirmation writes the subject and message once, for you to read, edit and save. Nothing is written per response: every person gets the confirmation you saved.
- What goes to the model: the form's name and description, its questions (labels, types and choices; hidden fields left out), your reply instructions, whether the form has a booking link, and your workspace's name. Never anyone's answers. The same AI provider as triage (OpenAI, with
store: false). - The rules it follows: confirm the form came in and thank them; say what happens next only as your instructions say; never invent prices, dates, response times, links or promises; use only the merge fields above; sign off as the team, not a person.
- A plain starter instead for forms with triage turned off (nothing from those forms goes to the AI provider), when Mirio has no AI provider set up, and when the model's draft doesn't follow the rules (a made-up link or merge field, say). The button then reads Use a starter.
API
GET /v1/project/forms/{slug}/confirmation (secret key) returns:
{
"confirmation": { "enabled": true, "subject": "Thanks, {{first_name}}", "body": "Hi {{first_name}},\n\n…", "replyToUserId": null },
"paused": null,
"effective": {
"sends": true,
"from": "\"Acme\" <[email protected]>",
"replyTo": "[email protected]",
"bookingUrl": "https://cal.com/acme/demo",
"preview": { "subject": "Thanks, Ada", "text": "Hi Ada,\n\n…" },
"warnings": ["Confirmations come from Mirio’s address (…), at most 50 a day from a form, until the workspace has a verified sending domain (Settings → Email in the dashboard; then up to 200)."]
},
"mergeFields": [{ "name": "first_name", "description": "…" }, …],
"starter": { "subject": "Thanks for getting in touch with {{workspace_name}}", "body": "…" }
}
effective.sends is true when a response that qualifies gets a confirmation right now; warnings say what stops it (the site isn't claimed, the form has no email question, {{booking_link}} without a booking link, a pause) or is worth knowing. paused is null, or why Mirio paused the form's confirmations: { "reason": "complaint" | "bounces", "at": "…", "bounced"?: 3, "sent"?: 20, "why": "…" }. starter is a plain template to begin from.
PUT replaces the settings with { "enabled"?: boolean, "subject"?: string | null, "body"?: string | null, "replyToUserId"?: "usr_…" | null } (null or "" clears one; the CLI merges for you) and returns the same shape. The subject is one line of up to 150 characters, the message up to 3,000. It answers 400 invalid_request with fields and a hint for an unknown or unclosed merge field, turning it on without a subject or message, {{booking_link}} on a form without a booking link, or a replyToUserId who isn't a member of the workspace.