Troubleshooting
Start with the error code: every API error includes code and docs_url, and
Error codes explains each one. History shows every accepted message with
its timeline, the provider response and the rendered content.
The request fails
Section titled “The request fails”403 origin_not_allowed – Add your site’s domain to Security → Allowed websites (and
localhost for development). Check the exact host, including www.. With
details.reason: "no_origin" the request had no Origin at all (native app, server script):
turn on Allow mobile apps for apps, use a private key on servers. See
Allowed origins.
403 private_key_in_browser – A private key was used from a web page. Use the public key in the
browser and keep the private key on your server. See Keys.
403 captcha_required / captcha_failed – The template requires Turnstile. Render the widget
inside the form or pass turnstileToken; reset the widget after each attempt, because tokens are
single-use. captcha_failed with hostname_mismatch means the widget was solved on a hostname that
isn’t the request’s origin – add every hostname of your site to your Turnstile widget. See
Turnstile.
403 captcha_not_configured – The template requires the CAPTCHA, but your organization hasn’t
added its own Turnstile keys. Create a free widget in Cloudflare and paste its site key and secret
key into Security → Bot protection (Turnstile), or turn the CAPTCHA off on the template.
422 invalid_template_params – Look at details.issues: each issue names the param and
the rule. Common causes: a required field is empty, an email param got a name, a number param
got "12 pcs", a list param got a string that isn’t JSON.
403 dynamic_recipient_not_allowed – The template’s To/CC/BCC uses a param. Use an
auto-reply for confirmations, or enable Allow dynamic recipient in
the template’s Settings → Protection.
429 rate_limited – You exceeded the plan, IP or key rate. The SDK already waited and
retried. For bulk sends from a server, use batch sends.
CORS error in the browser console – Usually a different error in disguise: the request was
blocked by a Content-Security-Policy (connect-src), an ad blocker, or went to a wrong apiUrl.
The Frontmail API sends CORS headers for all origins; access is controlled by allowed origins.
The request succeeds but no email arrives
Section titled “The request succeeds but no email arrives”- Find the message in History.
held– you are out of credits. Buy a pack (Buy credits) or wait for renewal; it will be sent automatically. It is also listed in Held messages. See Hold queue.held_service_error– your service’s credentials stopped working. Open Email services, fix or reconnect the service; held messages are sent automatically afterwards.failed– open the message: the provider’s error is shown in the timeline. Typical causes are an unverified sender domain or an invalid recipient.sent– the provider accepted it. Continue below.
- Check spam. If it’s there, set up SPF, DKIM and DMARC for your From domain as described in
your provider guide, and use a From address on that domain (not a
@gmail.comaddress through a transactional provider). - Check the suppression list. Sends to suppressed addresses fail with
451, but if you removed the address from the list after a bounce, the mailbox may still not exist. - Set up webhooks to see
delivered/bouncedevents instead of guessing.
The service keeps becoming unhealthy
Section titled “The service keeps becoming unhealthy”- Gmail: Google revokes refresh tokens when the password changes, when the user removes the app’s access, or after 6 months without use. Reconnect the service.
- API providers: the key was rotated or its permissions reduced. Create a key with send permission and update the service.
- SMTP: the mailbox password changed, or the provider requires an app password when 2FA is on.
Add a fallback service so a broken service doesn’t stop your forms while you fix it.
Emails look wrong
Section titled “Emails look wrong”- Styles missing in Gmail or Outlook → enable Inline CSS on save in the template’s Settings.
- Text is cut off with “View entire message” → the email is over ~100 kB; remove inline images or large styles.
- Literal
<b>in the email → the value was escaped. Use anhtmlparam if you really need markup (see escaping). - Dates in the wrong language or time zone → set the template locale and your organization time
zone, or pass
timeZone=toformatDate.
Still stuck?
Section titled “Still stuck?”Email support@frontmail.dev with the message_id (or the code and time of the error). Never
include your private key.