Troubleshooting
Find what’s wrong and get your form working again. Each section starts from what you see, shows how to check it, and gives the fix. Formerie returns stable codes, and this page uses their exact names.
The sections:
- My Email Didn’t Arrive
- A Submission Was Rejected
- The Form Isn’t Showing
- A CORS Or Origin Error
- Verification Failed
My Email Didn’t Arrive
Symptom: someone filled in the form, and the email never showed up.
How to check: open Deliveries in the app and find the response by form and date. Each row has a status, and a diagnostic with a code, a summary, and a next step. The same details are on the response itself under Submissions. Then find what you see in this table.
| What you see | What it means | What to do |
|---|---|---|
| No response in Submissions | It was refused before it was stored | See A Submission Was Rejected |
| A response with no delivery | The form had no recipients, so it was only stored | Add a recipient under Delivery. New responses get email from then on. Earlier ones were never sent, and a retry doesn’t send them |
| Queued | Waiting for its attempt | Wait a moment |
| Retrying | A temporary problem with the provider. Formerie tries up to 5 times, 60 seconds apart | Wait, unless the diagnostic is DELIVERY_STALLED |
| Failed | The email couldn’t be sent | Find the code in the next table, fix it, then choose Retry delivery |
| Delivered | The provider accepted the email | See Delivered, But Not In The Inbox |
Delivery Diagnostic Codes
| Code | What the app says | Fix |
|---|---|---|
DELIVERY_RECIPIENTS_UNAVAILABLE | No active verified recipients are configured for this form | Have the recipient confirm by email, or add another one, under Delivery |
DELIVERY_SENDER_UNAVAILABLE | The selected sender identity is not active or verified | Confirm the sender under Senders, or set Send from back to the Formerie default |
DELIVERY_CREDENTIALS_MISSING | The delivery provider is missing required credentials | Reconnect the provider under Destinations |
DELIVERY_PROVIDER_NOT_FOUND | The configured email provider could not be used | Review the provider under Destinations and on the Delivery tab |
DELIVERY_PROVIDER_UNREACHABLE | The delivery provider could not be reached | Check the provider’s status, then retry |
DELIVERY_PROVIDER_ERROR | The provider did not accept the delivery | Check the provider’s status and your settings with them, then retry |
DELIVERY_FAILED | Delivery failed before the provider accepted the message | Review the sender, recipients, and provider, then retry |
DELIVERY_STALLED | Delivery stopped retrying after a temporary error | Open the response and retry |
DELIVERY_PROVIDER_UNREACHABLE_RETRYING | The delivery provider could not be reached | Nothing yet. Formerie will try again |
DELIVERY_PROVIDER_TEMPORARY_ERROR | The provider returned a temporary delivery error | Nothing yet. Formerie will try again |
DELIVERY_QUEUED, DELIVERY_WAITING_FOR_ATTEMPT | Delivery is waiting for an attempt | Nothing yet |
DELIVERY_ACCEPTED, DELIVERY_COMPLETED | The provider accepted the email | See the next section if it still hasn’t arrived |
Delivered, But Not In The Inbox
Delivered means the provider took the email. It doesn’t prove it reached the inbox.
- Ask the recipient to check their spam folder.
- If you use your own email provider, search its logs for the message.
- Open the form’s Delivery tab, and make sure that address is on the form.
- Open Recipients, and look at that address. A Pending, Disabled, or Suppressed recipient gets nothing. A suppressed address usually bounced or marked a message as spam.
Only Some People Got It
A recipient with a condition only gets the responses that match it. Compare the condition on the Delivery tab with the answers in the response. If a condition leaves nobody to send to, the response is kept and no email goes out, and that isn’t counted as a failure.
Retry A Delivery
Fix the cause first, because most failures repeat otherwise. Then open the response under Submissions and choose Retry delivery. It sends real email with the form’s current delivery settings, and doesn’t change the response.
| Code | Meaning |
|---|---|
DELIVERY_NOT_RETRYABLE | This delivery can’t be retried right now, for example because it was already delivered |
DELIVERY_ATTEMPT_IN_PROGRESS | An attempt is running. Wait for its result |
QUEUE_FAILED | The retry couldn’t be queued. Try again shortly |
A Test Email Won’t Send
| Code | Cause | Fix |
|---|---|---|
FORM_DELIVERY_TEST_NOT_READY | A sender or recipient isn’t ready, or the form has no recipients | Follow the items under Delivery readiness |
FORM_DELIVERY_TEST_NO_RECIPIENTS | The sample answers match no recipient | Change the sample answers under Preview, or review recipient conditions |
FORM_DELIVERY_TEST_RATE_LIMITED | You can send one test per minute | Wait a minute |
FORM_DELIVERY_TEST_SEND_FAILED | The provider refused it | Check the sender and provider, then try again |
A Submission Was Rejected
Symptom: the visitor sees an error, or your code gets ok: false.
How to check: a refused submission isn’t stored, so it never appears under Submissions. Read the code in the response
instead, in your browser’s network panel or from result.error.code in the SDK. It looks like this:
{
"ok": false,
"error": {
"code": "SPAM_REJECTED",
"message": "The submission was rejected."
}
}The server runs its checks in order and stops at the first one that fails: origin, availability, rate limit on public routes, response window, size, format, repeat send, submission limits, spam checks, human verification, answers, and quota.
| Code | HTTP status | Cause | Fix |
|---|---|---|---|
VALIDATION_FAILED | 422 | An answer failed a check | See Failed Answers |
SPAM_REJECTED | 403 | A spam check caught it | See Rejected As Spam |
TURNSTILE_FAILED | 403 | Human verification failed, with any provider | See Verification Failed |
RATE_LIMITED | 429 | Too many submissions in a short time | See Too Many Submissions |
FORM_QUOTA_REACHED | 409 | The form hit its Maximum accepted submissions | Raise or clear it under Submission limits, in Settings |
DUPLICATE_SUBMISSION | 409 | This metadata.idempotencyKey was already accepted | Treat it as a success. See Repeat Sends |
FORM_NOT_OPEN | 403 | Before the Response window opens | Change the window on the Publish tab |
FORM_CLOSED | 403 | The form is closed, or its window has ended | Change Public access or the window on the Publish tab |
FORM_MAINTENANCE | 503 | Public access is on Maintenance | Set it back to Public |
FORM_NOT_FOUND | 404 | Wrong address, or the form isn’t published | See The Form Isn’t Showing |
FORBIDDEN_ORIGIN | 403 | The browser’s site isn’t an allowed origin | See A CORS Or Origin Error |
BAD_REQUEST | 400 or 413 | The body has no fields object, or is over 100,000 bytes | Send { "fields": { ... } }, and shorten large answers |
INVALID_JSON | 400 | The body isn’t valid JSON | Fix what your code sends |
QUEUE_FAILED | 503 | The email couldn’t be queued | Ask the visitor to try again shortly |
INTERNAL_ERROR | 500 | Something went wrong on our side | Try again shortly |
Failed Answers
VALIDATION_FAILED lists each problem in error.fields and error.issues, with a path, a code, and a message:
{
"ok": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "The submitted form contains invalid fields.",
"fields": {
"contact.email": [
{
"path": "contact.email",
"code": "FIELD_EMAIL_INVALID",
"message": "Invalid email address.",
"rule": "email",
"params": {}
}
]
},
"issues": [
{
"path": "contact.email",
"code": "FIELD_EMAIL_INVALID",
"message": "Invalid email address.",
"rule": "email",
"params": {}
}
]
}
}Show each message next to its field. Hosted pages and embeds do this for you. The codes are listed by field type in the Form Definition Reference, and your own validators return the code you gave them.
A field that’s required but hidden can’t fail, because the server skips hidden fields. If a required field keeps failing, check that your own UI sends it under the exact name, with groups as nested objects.
Rejected As Spam
The response doesn’t say which check caught it, so go through all three under Settings, in Anti-spam:
| Check | What trips it | Fix |
|---|---|---|
| Hidden field | The hidden field has a value. New forms use _formerie_hp | In your own UI, leave that field out or empty |
| Minimum submit time | metadata.startedAt is more recent than the limit, 1 second on new forms | Set startedAt when the form first appears, not on submit, or lower the limit |
| Maximum links | More links than the limit across all answers, 3 on new forms. Web addresses in url fields count too | Raise the limit. 0 rejects any link, and an empty setting turns the check off |
Too Many Submissions
RATE_LIMITED comes from one of two places:
- Submission limits, under Settings. New forms allow 10 submissions per visitor every 10 minutes, and 300 for the whole form every hour. Raise them if your form gets real bursts. People on the same network, such as an office, share the per-visitor limit.
- A short-term limit on public routes, which you can’t change. It sends
Retry-After, in seconds.
Tell the visitor to wait, and keep their answers on screen.
Repeat Sends
DUPLICATE_SUBMISSION means the first send already worked, and that’s good news. Show your success message. Send a new metadata.idempotencyKey for each new submission, and reuse a key only to retry the same one.
The Form Isn’t Showing
Symptom: the link shows an error, or the page where you embedded the form is blank.
How to check: open the form’s Hosted URL in a private window. If it works there, the form is fine and the
problem is in the page around it. If you see a line of JSON, read its code.
| What you see | Cause | Fix |
|---|---|---|
FORM_NOT_FOUND | The form was never published, Public access is on Unpublished, or the address is wrong | Publish on the Publish tab, and copy the address from there |
FORM_CLOSED, FORM_NOT_OPEN | The form is closed, or outside its Response window | Change them on the Publish tab |
FORM_MAINTENANCE | Public access is on Maintenance | Set it back to Public |
FORM_NOT_FOUND on your own hostname | The hostname isn’t active or isn’t connected to this form | Finish the DNS steps under Domains, and check its connection |
| “The form could not be loaded.” or “Loading form…” | The browser couldn’t load the form’s script | Check for a blocked request to cdn.formerie.com in the browser’s network panel |
| An old version of the form | Your changes are still a draft | Choose Publish changes |
| A question is missing | A condition is hiding it, or it’s on a later step | Review Conditions and Flow |
An Embed Shows Nothing
- The script adds its iframe right where the tag sits. With
data-formerie-target, the selector has to match something on the page when the script runs. data-formerie-formmust beworkspace-handle/form-handle, as in the Embed code. A wrong value showsFORM_NOT_FOUNDinside the iframe.- If your site sets a Content Security Policy, allow
https://forms.formerie.comas a frame and script source, andhttps://cdn.formerie.comas a script source.
A Browser Mount Shows Nothing
The browser mount loads the form with a call from your page. Without your site’s address in the allowed origins, that call fails. See the next section.
A CORS Or Origin Error
Symptom: your browser’s console shows blocked by CORS policy, your code gets a network error such as Failed to fetch or the SDK’s NETWORK_ERROR,
and the request shows as 403 or 404 in the network panel. Your code doesn’t get to read a Formerie code, because a refused browser
request comes back without the headers that would let it.
How to check: this only happens when browser code on your own site calls Formerie, which means the browser mount, a
framework package used in the browser, or your own fetch. A hosted link and a script embed don’t call from your site, so they
can’t cause it.
Open the failed request in the network panel. It’s often the OPTIONS one, and its response says FORBIDDEN_ORIGIN or FORM_NOT_FOUND. Compare the request’s Origin header with your list.
| Cause | Fix |
|---|---|
| Your site isn’t listed | Add it under Your website addresses on the Publish tab, or Allowed origins in Settings, then publish |
| You added it but didn’t publish | Origins take effect when you publish. Publish saves them for you |
| The address differs slightly | Matches are exact. https://example.com, https://www.example.com, and http://example.com are three entries, so add each one your site answers on |
| You’re testing locally | Add your dev address with its port, such as http://localhost:5173 |
| You used a path or a wildcard | Use the address only. Paths fail with FORM_CONFIG_ORIGIN_INVALID, and * with FORM_CONFIG_ORIGIN_WILDCARD |
The response says FORM_NOT_FOUND | The workspace or form handle is wrong, or the form isn’t published. It looks like a CORS error because no form means no list of origins to allow |
| You added your own request header | Browsers may only send Content-Type here. Remove the custom header, or send it from your server |
Code on your own server doesn’t need any of this, unless it forwards an Origin header. Without one, the check doesn’t apply. See Allowed Origins.
Verification Failed
Symptom: a submission returns TURNSTILE_FAILED. It’s the same code for Cloudflare Turnstile and for reCAPTCHA, and the
response doesn’t give a reason.
How to check: look at the form’s Human verification settings under Settings, then go through these causes.
| Cause | Fix |
|---|---|
| No token was sent | Your own UI needs to pass the provider’s token as verificationToken. Hosted pages, embeds, and the browser mount handle this for you |
| The token was already used, or expired | Tokens work once. Get a new one for each attempt, including after a failed submit |
| The widget’s domain doesn’t match | Formerie checks that the token came from the site making the request. See the next section |
| Formerie managed on your own site | Managed keys work on supported Formerie-hosted domains. Use your own keys for your own domain |
| Wrong keys | The site key and secret key must come from the same widget. Save them again under Human verification |
| Expected action doesn’t match | It must match the action your widget sends |
| reCAPTCHA v3 score too low | Lower Minimum score, from 0 to 1 |
Which Domain To Add To Your Widget
With your own keys, the provider’s console lists the domains your widget works on. Add the domain the widget loads on:
| How you publish | Domain to add |
|---|---|
| Hosted link | forms.formerie.com, or your own connected hostname |
| Script embed | forms.formerie.com, or your own connected hostname, because the widget runs inside the iframe |
| Browser mount or your own UI | Your own site |
Verification Through Your Own Server
A token from your page, sent on through your own server, can fail on the domain check. Without an Origin header, Formerie
compares the token’s domain with its own. To pass, forward the visitor’s Origin header with the request, and list that site
under allowed origins too.