Bandage for Craft CMS

FAQ

Do I have to change my form?

You add one line to it. {{ craft.bandage.form() }}, anywhere inside the <form>.

Everything else stays: contact-form/send is still the action you post to, submission is still Contact Form's model, submission.getErrors() still behaves as Contact Form documents it. Bandage attaches through the three events Contact Form fires and never forks it.

What does it cost?

Lite is free, permanently, and stores submissions, filters spam, validates attachments, exports, prunes and sends the digest.

Pro is $79, with a $29 annual renewal for continued updates. It adds multiple form profiles, field validation rules, recipient routing, conditional redirects, the autoresponder and integrations. See Installation for the table.

What happens if I downgrade?

Pro configuration stays exactly where it is and stops applying. Nothing is deleted. Your routing rules live in project config, they survive the downgrade, and they start working again the moment you upgrade.

Stored submissions are untouched either way — they are Lite features.

Does it replace Contact Form?

No, and that is the whole design. Contact Form is a dependency, not a thing to be superseded. It still takes the POST, still validates, still sends. Bandage adds a record, a spam filter, and somewhere else for the message to go.

If you uninstall Bandage tomorrow, your form keeps working, because it is still Contact Form's form.

What happens to my data if I remove it?

Uninstalling drops Bandage's tables, and that takes the stored submissions with them. Export first:

php craft bandage/submissions/export --path=/tmp/submissions.csv

Your form profiles are project config, so they persist in project.yml until you remove them there.

The emails themselves were always sent by Contact Form and are wherever they were sent. Nothing about your existing mail is contingent on Bandage.

Will it lose messages?

A failure to store never fails a send. If the database is unavailable at the wrong moment, the email goes out and the archive misses one — deliberately, because the person who filled in the form doesn't know Bandage exists, and losing their enquiry to an archive is the wrong trade.

In the other direction, every spam action stores the message. Quarantine, reject and flag all keep it. A spam folder nobody can review is deletion with extra steps.

Will it block real people?

It can, which is why the defaults are conservative and why nothing vetoes on its own.

Checks score rather than reject, so a honeypot hit alone — a password manager filling a hidden field — is forgivable in a way that a honeypot hit plus a twelve-link body is not. The default action is quarantine: stored, not sent, and the visitor sees the ordinary success message.

If you are tuning, use the flag action for a week. It sends everything and marks what it would have caught, so you can see the effect on real traffic before it has one.

Does the visitor find out they were caught?

Not on the default action, no. A quarantined submission gets the ordinary success message, because the bot must not learn it was caught — a spam filter that announces itself is a spam filter that gets tuned around.

The reject action does show an error, and exists for forms where a false positive needs to be told to try another way.

What does it store about people?

By default: the message, the sender's name and address, the extra fields your form posts, the IP address, user agent and referrer, and the delivery result.

You can turn off metadata, turn off the body, delete after N days, delete spam sooner, or blank stored IP addresses while keeping the message. All of it is off by default — retention is a legal question with a technical answer, and quietly deleting somebody's correspondence is the worse way to be wrong.

Can I have more than one form?

On Pro, yes, as many as you like. Each has its own recipients, spam settings, rules and autoresponder, and a template opts into one by name:

{{ craft.bandage.form('support') }}

On Lite there is one profile, default, and every form uses it.

Are submissions real Craft elements?

Yes. Which means the element index, search, sources, exporters, the trash, per-user permissions and a field layout are Craft's own rather than a second implementation of each.

The field layout is the part people miss: it is where your team records what happened next — who followed up, what the outcome was — on the same screen as the message. It is not the visitor's form; their answers arrive as whatever message[…] keys your Twig posts.

Why a reference code instead of the ID?

Because a reference gets read down a phone, and K4TP-9WQR has no I, O, 0 or 1 in it to be misheard.

And because quoting element ID 4,182 to somebody tells them how much mail you get.

Do I need a CAPTCHA?

Not to start with. The honeypot and the time trap are on by default and cost nothing — no third party, no network call, nothing for the visitor to do.

Add one when the free checks stop keeping up. reCAPTCHA v2 and v3, hCaptcha and Cloudflare Turnstile are all supported; Turnstile asks the least of your visitors.

What if the CAPTCHA provider goes down?

Verification fails open on an outage and closed on a genuine failure. A network error reaching Google is not evidence about the visitor; a returned "no" is. A CAPTCHA outage that silently blocked every enquiry would be worse than the spam it kept out.

Is the autoresponder safe to switch on?

Yes, with the cap left where it is. It is rate limited to three per hour per recipient address by default, and that is an abuse control rather than a nicety: an autoresponder is a mail relay whose destination comes from the request body. Without a cap, anyone can submit the form in a loop with a third party's address in it and have your domain — with your real SPF record — mail-bomb them.

Can I send submissions somewhere other than email?

Signed JSON webhooks, Slack and Microsoft Teams, on Pro. Everything is delivered on the queue, so an endpoint that has gone down never holds the visitor's browser open and never fails their submission.

Webhooks carry X-Bandage-Signature: sha256=…, an HMAC over the exact bytes sent.

Will the CSV export open safely in Excel?

Yes. Cells beginning =, +, - or @ are prefixed before they are written. Every value in that file came from a stranger, and =HYPERLINK("http://evil","Click") in a name field is a live link the moment somebody opens it.

Does it work with Ajax submissions?

Storage, spam filtering, validation, routing, the autoresponder and integrations all work exactly the same. Conditional redirects do not apply — an Ajax submission gets JSON rather than a redirect, so there is nothing to rewrite.

Which versions does it need?

Craft CMS 5.3+, PHP 8.2+, and craftcms/contact-form 3.x. Contact Form 2.x is the Craft 3 line — an easy one to get wrong, since the version numbers do not line up with Craft's.

Can I add my own spam check?

Yes:

Event::on(Spam::class, Spam::EVENT_AFTER_SPAM_CHECK, function(SpamCheckEvent $e) {
    if (str_ends_with((string)$e->submission->fromEmail, '.example')) {
        $e->verdict->add('ourCheck', 10, 'reserved TLD');
    }
});

Your check contributes a score and a reason like any built-in one, and the threshold decides what happens to it. See Usage.

Where do I get help?

justin@justinholt.com. Include the submission reference and the score breakdown from its detail screen — that is usually the whole answer.