Bandage for Craft CMS

Troubleshooting

Start here

Almost every report falls into one of four shapes. Find yours.

SymptomUsually
The email arrives, but Submissions is emptyThe Twig tag is missing
Nothing arrives and nothing is storedContact Form itself, not Bandage
Nothing arrives but it is stored, marked spamThe score reached the threshold
Real people are being caughtThe time trap or a blocklist, usually

The submission screen shows the score and every check that contributed to it. That list is the answer to most of these questions, so look there before changing settings.

Submissions is empty, but the email arrived

Contact Form sent the message without Bandage ever seeing it, which means the form has no {{ craft.bandage.form() }} in it. View source on the live page and look for a hidden input named bandageForm. If it isn't there, the tag isn't in the template that actually rendered.

Watch for a second, older contact template that nobody remembers — a footer form, a modal, a landing page — still posting to contact-form/send without the tag.

If the input is there, check Store submissions in the plugin settings and Store on the form profile. Either being off is a deliberate no-record configuration.

Nothing arrives and nothing is stored

Bandage never ran, so the problem is upstream of it. In order:

  1. Is Contact Form itself installed and enabled? php craft plugin/install contact-form.
  2. Does Contact Form have a toEmail? Without one, and without recipients on the Bandage form profile, there is nowhere to send to.
  3. Is the form failing validation? submission.getErrors() will say. A missing or malformed fromEmail never reaches the send step.
  4. Check Craft's mail settings and the queue.

A failure to store never fails a send, by design — the person who filled in the form doesn't know Bandage exists, and losing their enquiry to an archive would be the wrong trade. So "stored but not sent" and "sent but not stored" are different faults with different causes.

It's stored, marked spam, and it shouldn't be

Open it. The score breakdown names every check that fired.

Honeypot fired on a real person. Password managers fill hidden fields. That is exactly why the default score is 10 against a threshold of 5 rather than an outright veto — but if it happens repeatedly, rename the honeypot field in the plugin settings. Some managers key off the field name.

Time trap fired. Two directions:

  • Too fast — the minimum is 3 seconds by default. A form above the fold on a page somebody has already read once is genuinely submitted in two. Lower it.
  • Too slow — the maximum is 86400 seconds. A form left open in a tab overnight trips it. Raise it, or set it to 0 for no upper bound.

Blocked words. They match anywhere in the submission, including inside longer words and inside email addresses. A blocked word of cash catches somebody at cashman.co.uk.

Require MX fired. The sender's domain has no MX record. Genuinely misconfigured domains exist, and so do domains that only accept mail through a provider that answers slowly. This check costs a DNS lookup on every submission; turn it off if it is causing more trouble than it stops.

CAPTCHA fired. See below.

To recover a message: Mark as not spam on the submission, then Resend. Nothing was destroyed — every spam action stores the message, which is the reason quarantine is the default.

Spam is getting through

Look at what the delivered spam scored. If it scored 0, none of your enabled checks saw anything; if it scored 4 against a threshold of 5, you are one point away.

In rough order of how much they help per unit of risk:

  1. Turn on rate limiting5 per hour per IP stops volume without judging content.
  2. Turn on link counting at 3 or so. The cheapest signal there is.
  3. Add a CAPTCHA. Turnstile is the least intrusive of the four.
  4. Lower the threshold to 4, and watch the flagged submissions for a week.

Use the flag action while tuning. It sends everything and marks what it would have caught, so you can see what a threshold change would have done to real traffic before it does it.

Turn on Require MX and Disposable email last. They are the two most likely to catch a real person.

CAPTCHA problems

Nothing renders. The site key is empty, or the template passes { captcha: false }, or the form profile the template names isn't the one you configured. Check the hidden bandageForm value against the handle in the CP.

Everything is scored as a CAPTCHA failure. The secret is wrong, or belongs to a different site key, or the domain isn't on the provider's allow-list.

reCAPTCHA v3 catches real people. Lower the minimum score. 0.5 is Google's suggestion, not a law; 0.3 is a reasonable floor for a contact form.

Verification fails open if the provider can't be reached at all and closed on a returned failure. So a provider outage will not block your enquiries — and if enquiries are being blocked, the provider answered, and the answer was no.

Field validation isn't firing

Field rules are Pro. On Lite the rules are saved and not applied.

If you're on Pro:

  • The rule's field is a value path, not an input name. message[Phone] in the template is message.Phone in the rule.
  • Every rule type except required passes on an empty value. Add a required rule alongside it.
  • Contact Form's own errors come first. If fromEmail is invalid, Bandage's rules are skipped entirely that time round rather than burying the one error the visitor can act on.

Routing isn't routing

  • Routing is Pro.
  • Rules run in order, and the first match stops unless you turn that off. A catch-all rule with no conditions matches everything — put it last, or it will match first.
  • Check the condition field spelling: message.Department, not Department, and not message[Department].
  • The submission screen records the addresses it actually used. Compare them to what you expected before assuming the rule didn't match.

The autoresponder isn't sending

  • It is Pro, and it is off by default.
  • It is rate limited to 3 per hour per recipient address. Testing repeatedly with your own address hits that cap on the fourth try, which looks exactly like it stopped working.
  • If the autoresponder is conditional, check the conditions the same way as routing.
  • It only fires on a message that was actually sent. Quarantined spam never reaches that step.

Webhooks aren't arriving

  • Pro, and delivered on the queue — so check the queue is running. A stalled queue looks like a broken webhook.
  • The endpoint must be http:// or https://. Anything else is refused at save.
  • Spam is excluded by default. Turn on include spam if you meant to receive it.
  • Signature mismatches on the receiving end are almost always the body being re-serialised before it is verified. The HMAC is over the exact bytes sent, not over a re-encoded object.

Attachments aren't saved

  • Attachments are off by default, and need a volume.
  • A file over the size limit, past the count limit, or with a disallowed extension produces a visible error on the form rather than a silent drop.
  • Extensions fall back to Craft's allowedFileExtensions when the list is empty, so a file Craft won't accept anywhere is refused here too.

Everything broke after a deploy

Forms are project config. If a form disappeared, it was removed from project.yml somewhere and applied here. The submissions it took are unaffected — formHandle is stored as a snapshot string precisely so that deleting a form never takes its correspondence with it.

Still stuck

Craft's logs carry a bandage category. Take the submission's reference, the score breakdown and the log lines around that timestamp, and email justin@justinholt.com.

Next

  • Configuration — every setting and what it defaults to
  • FAQ — editions, privacy, and what happens if you remove it