Skip to content
TrustList
Blog

How bots turned our contact form into a spam relay, and what we changed

Community contribution

By Tech Style Ltd · community contribution, published under the author's own name

Bots used our contact form's automatic reply to deliver spam to 2,281 addresses in four days. How the attack works, how we traced it through our mail logs, and what we changed on the website and the mail server.

About How bots turned our contact form into a spam relay, and what we changed

How bots turned our contact form into a spam relay, and what we changed

A guest post by Tech Style Ltd, a software company registered in England and Wales. It is the account of an attack on our own website in September 2026: what happened, how we traced it, how we stopped it, and what we would tell anyone who runs a contact form.

For four days in late September, our website's contact form sent email to people who had never heard of us. At first glance it did not look like much: a short, polite note on our letterhead saying we had received their message and would reply within a business day. But each note carried a few lines of text we had not written, and a link we would never have sent. By the time we closed the hole, our mail server had delivered that note to 2,192 strangers, correctly signed and authenticated as coming from us.

Nobody stole a password. Nobody broke into a server. The attack used our form exactly as it was built. That is what makes this kind of abuse worth writing about, because a great many small company websites are built the same way ours was.

What happened, in brief

Our contact form did two things whenever someone filled it in. It emailed the enquiry to our team, and it sent the visitor an automatic acknowledgement so they knew their message had arrived. The acknowledgement was friendly. It addressed the visitor by the name they had typed, and it repeated their message back to them under the heading "for reference, here is what you sent".

On 25 September at 16:57 UTC, a bot began filling in the form. In the name field it typed a nonsense word, "Infirevini". In the email field it typed the address of someone else, a different stranger each time. In the message field it typed a short lure, such as "Photos for my escort application are uploaded. Let me know if the quality is good. Preview:" followed by a shortened link. Our form did what it was built to do. It thanked "Infirevini" for getting in touch, repeated the lure and the link, and sent the result to the stranger's inbox from our own address.

Between 25 and 28 September the form sent 4,572 emails. In the seven weeks before, it had sent 28. The 2,285 automatic replies in that period went to 2,281 different outside addresses, and 2,192 of them, 96 per cent, were delivered.

We stopped it at 20:29 UTC on 28 September by changing how the form works. At the time of writing, not one bot submission has reached our mail server since.

What this attack actually is

The attack has a few names. People call it contact-form abuse, form relay spam or auto-responder abuse. The mechanism is always the same: find a website that sends an email to an address the visitor supplies, and arrange for that email to carry the attacker's words.

It is worth being precise about why an attacker would bother, because the reason is also why the attack works so well.

A spammer who sends email from their own servers has a reputation problem. Their domains are new, their servers are known, and the large mailbox providers have seen their messages before. A real company's mail server has the opposite profile. Its domain has a history. Its messages are signed with DKIM, its servers are listed in its SPF record, and its DMARC policy tells receivers that mail claiming to be from it can be checked. When that company's server sends a message, it arrives with every credential a receiving system looks for.

Contact-form abuse lets an attacker borrow all of that. The spam does not pretend to come from us; it really does come from us. Our server composed it, signed it and delivered it. The DMARC reports that mailbox providers send us show every one of our messages they saw in that period passing SPF, DKIM and DMARC. Those checks answer the question "did this domain send this message?", and the honest answer was yes. They were never designed to answer "did this domain mean to say this?".

This is also why the lure was short and odd. A recipient who saw an email from an unknown UK software company, thanking them for a message about photos, might well click the link out of confusion or curiosity. The link was a URL shortener, which hides the destination until the click. We have not followed those links and we do not publish them, but lures of this shape usually lead to adult-content scams, fake login pages or malware downloads.

Two related attacks are often confused with this one.

  • Subscription bombing, also called list bombing, uses thousands of sign-up and contact forms to flood a single victim's inbox, often to bury a security alert or a bank notification under noise. Our case was different: 2,285 replies went to 2,281 addresses, so almost every victim received one message. This was spam delivery, not a flood aimed at one person.
  • Backscatter is the stream of bounce messages that reach innocent people when spammers forge their address as the sender. In our case the bounce notices came back to us, because the mail genuinely was ours.

What we experienced sits between the two: a real sender's legitimate machinery, used to carry someone else's message to strangers, one at a time.

The timeline

All times are UTC.

  • Before 25 September. The form behaved like the contact form of a small company. It sent 28 emails in seven weeks, a notification and an acknowledgement for each genuine enquiry.
  • 25 September, 16:57. The first bot submission arrived. Within the hour the form was sending 40 emails an hour; by 19:00 it was sending more than 80.
  • 25 September, 18:24. Our mail server logged its first connection timeouts to Apple's mail servers, and timeouts to Microsoft's appear in the log too. Both providers were slowing down a sender whose volume had jumped from almost nothing.
  • 26 September, 02:00. The busiest hour of the attack: 100 emails, one every 36 seconds.
  • 26 September, afternoon, to 27 September, afternoon. The rate fell to mostly 25 to 35 emails an hour for almost a full day, then climbed back to 80 and 90. We changed nothing on our side during that lull; the rate appears to have followed the bots' own capacity.
  • 28 September, evening. Our team shipped two changes to the form: a second step that must be passed before anything is sent, and an acknowledgement that no longer repeats anything the visitor typed. The last bot email left our server at 20:29.
  • 29 September. A stack of "Delayed Mail" and "Undelivered Mail Returned to Sender" notices in our no-reply mailbox prompted a full investigation of the mail server's logs. That investigation produced most of the numbers in this post.
  • 30 September. We held back the replies still waiting in the outgoing queue, set sending limits on our automated mailboxes, cleared the notices, and collected the bots' IP addresses from the notifications in our inbox.

How we noticed

The first sign was the most obvious one. Our enquiries inbox filled up with messages from "Infirevini", every one a general enquiry, every one saying the sender had found us through Google.

We kept 2,158 of those notifications. At the height of the attack a new one arrived every minute or so. An inbox like that is not only a nuisance. The real enquiries are in there too, and a team that is deleting spam by the hundred will eventually delete a customer.

The inbox flood was what we saw. The more serious half of the attack, the replies going out to strangers, was invisible from our side. Nothing in the enquiries inbox showed us that each of those notifications had a twin travelling to someone else. We only learnt the full extent from the mail server, which is why the next section matters.

How we traced it

Starting with the bounce notices

The investigation started with the automated notices that had piled up in the mailbox our website sends from. There were 713 of them. On a first read they looked like a delivery disaster: row after row of messages that had not arrived.

They were not. Delivery status notifications follow a standard format (RFC 3464), and each carries a machine-readable "Action" field. When we parsed all 713, 696 said delayed. Our server was telling us it had tried to deliver a message, had been slowed down by the receiving provider, and was still trying. Only 17 said failed. The mail was not failing to get through. It was getting through slowly, and then getting through.

The notices also carried a copy of the original message. That was the moment the attack made sense. Every notice contained our own acknowledgement, addressed to someone we had never dealt with, thanking "Infirevini" and quoting a lure with a shortened link.

Following one message through the log

A mail server's log records every message it accepts and every attempt to deliver it. We picked one of the replies and followed it through.

The pattern was the same every time. Our website logged in to the mail server with the credentials of its own no-reply mailbox, handed over a message and logged out. A second later the server delivered that message to a mailbox provider, which accepted it.

One detail cost us time and is worth passing on. Mail servers accept messages from applications on more than one port, and our first count covered only one of them. By that count the website's mailbox had logged in only once in three months. Our own address was on thousands of outgoing messages, which made no sense. The website was using the other submission port. If you are auditing a mail server, count every way in.

Counting what went out

With the pattern clear, counting was straightforward. From 25 to 28 September, the website handed our mail server 4,572 messages: 2,287 notifications to our own inbox and 2,285 automatic replies to outside addresses. Those replies went to 2,281 different addresses, so the bots almost never reused a victim.

Gmail addresses made up the largest group, followed by Microsoft, Apple and Yahoo. That is roughly the shape of consumer email in general, which suggests the bots were working from a large, general list of addresses rather than targeting anyone in particular.

The delivery results were the uncomfortable part. Microsoft and Apple slowed us down; for a while our server paused deliveries to them altogether and retried later. That pause would also have delayed any genuine mail we sent to those providers in the meantime. But throttling is not refusal. Once the pauses expired the replies went through, and in the end 2,192 of the 2,285 were delivered: 767 of 798 at Gmail, 497 of 502 at Microsoft, 318 of 339 at Apple, 219 of 233 at Yahoo and AOL, and 391 of 413 elsewhere. Only 78 bounced outright. Fifteen were still in the queue when we put them on hold, and they will never be sent.

Checking that nothing else was compromised

An attack like this looks, from the outside, exactly like a stolen mailbox password, so we checked that possibility properly. Every login by the website's mailbox came from our web hosting, apart from one test message sent from the mail server itself in June. Over three months our mail server recorded 306 failed login attempts across all its mailboxes. That is the ordinary background noise of any server on the internet: password guessing against addresses such as info@ and admin@, much of it aimed at domains we do not even host. None of those attempts succeeded.

We also checked our standing with the wider email world. Our mail server's address was not listed on any of the major public blocklists we checked, which included Spamhaus, Barracuda, SpamCop, UCEPROTECT, PSBL and Mailspike, and neither was our domain. By 29 September both Microsoft and Apple were accepting our connections normally again. We were lucky there. A few more days at that volume and the story could have been different.

Who was behind it

Each notification in our inbox recorded the IP address the submission came from. Across the 2,158 notifications we kept, there were only 86 addresses. We matched each one to the network that owns it using public routing data. The 86 addresses sit on 36 networks run by 33 hosting companies, in 14 countries: 34 addresses in the United States, 15 in France, 11 in the United Kingdom, 9 in Canada and the rest spread thinly.

The most telling number is one we have not given yet. Not one of the 86 addresses belonged to a home broadband or mobile connection. Every one was a rented server in a data centre: virtual machines from mainstream cloud and hosting providers, several with names like "vps" or "instances" in their reverse DNS. This is what traffic from a rented proxy network looks like: cheap servers spread across many providers, so that no single address sends enough to stand out.

We name the providers only to describe the traffic accurately. They rent out servers; they were not the attackers, and most of their customers are ordinary businesses. The addresses themselves we have kept for our own records rather than publishing them, because an address that was abused last week may belong to someone innocent next month.

The spread of addresses explains why our existing protection failed. The form already had a per-address limit on how many messages one visitor could send in an hour. Against one bot on one machine, that limit works. Against 86 machines, each staying under the limit, it does very little. The busiest single address sent 224 submissions over the four days, a pace the per-address limit allowed.

What we changed on the website

Our changes followed one principle: the form should never let a visitor's words reach anyone but us.

The acknowledgement no longer repeats anything the visitor typed

This was the root cause, and the fix is the simplest part. The automatic reply now has fixed wording. It does not use the visitor's name, and it does not quote their message. It says we have their message, a person will read it, and they can reply to this email if it is urgent. An attacker who submits the form can still cause an acknowledgement to be sent to an address they choose, but the acknowledgement carries nothing of theirs. It is useless to a spammer.

It is worth saying plainly why the old design seemed reasonable. Repeating the visitor's message back is a courtesy: it reassures them that the right text arrived. Many form plugins do it by default. But a courtesy to the sender becomes a weapon the moment the sender and the recipient can be different people, and on a public form they always can be.

A second step before anything is sent

The form now works in two steps. The first press checks the fields and asks our server for a small sum, drawn as a picture. Nothing is posted at this stage. The second press sends the message, and only works with the right answer.

We built the check ourselves rather than adding a third-party service, and a few details make it harder to defeat than it looks:

  • The sum is drawn as a set of strokes, slightly jittered each time. The digits do not appear anywhere in the page's text, so a script reading the page cannot simply copy them.
  • The server signs each sum together with its answer, so the page cannot be handed a sum it did not issue.
  • Each sum expires, and there is a minimum time before it can be answered, because no person reads, types and sends a message in a couple of seconds.
  • Each sum can be answered once. A wrong answer uses it up, so a bot cannot keep guessing.

No challenge of this kind is unbeatable, and we do not pretend otherwise. A determined operator can pay people or software to solve pictures. The point is to change the economics: a form that used to cost an attacker nothing per message now costs something, and a spammer with thousands of other forms to choose from will usually move on.

A ceiling for the whole site, not only per visitor

Because the bots came from 86 addresses, per-visitor limits were never going to be enough. The site now keeps a single hourly budget for automatic replies across all visitors combined. Once the budget is used up, the form still accepts enquiries and still notifies our team, but it stops sending acknowledgements until the hour rolls over. A genuine visitor at a busy moment might not get a thank-you note, and we are content with that trade.

The same text twice is a flood, not a person

The site now remembers, for a day, a fingerprint of every message it receives. If the same text arrives again from anyone, the form accepts it quietly and sends nothing, to us or to anyone else. Very short messages such as "please call me" are exempt, because real people send those repeatedly. A spam run that pastes one lure thousands of times is reduced to one.

The existing protections stay in place: hidden honeypot fields that only bots fill in, the per-address limit, handling that stops a visitor from injecting their own email headers, and a copy of every enquiry kept on disk whatever the mail server does. The same protections now cover the other form on our site that sends a confirmation email.

What we changed on the mail server

The website fix stopped the attack. The mail server changes are about making sure that the next mistake, on any of our sites, cannot run for four days.

  • The replies still waiting were held back. Fifteen replies were still being retried when we investigated. We put them on hold so they will never be delivered.
  • Every automated mailbox has a sending ceiling. Each of our no-reply addresses now has an hourly limit, set a little above the busiest genuine hour that address has ever had. Had that limit existed in September, the attack would have been capped at a small fraction of what it achieved, whatever the website did. Our mail server also sends mail for our other products, so this protects more than one website.
  • The notices were cleared and the reports are read. We moved the 713 notices out of the way, and the aggregate DMARC reports that mailbox providers send us now go to a folder we review, rather than an inbox where they were easy to ignore.
  • We kept the evidence. The IP addresses, the counts and the timeline are kept with this incident's notes, so a repeat can be recognised quickly.

What it cost us

In money, very little. In other terms, more than we would like.

About 2,192 people received an email from our company that we did not write and would not have sent. Some of them will have reported it as spam, and each report counts against us with their mailbox provider. For a few days our genuine mail to Microsoft and Apple customers was delayed along with the spam. Our team spent hours clearing an inbox that was unusable, and more hours working out what had happened. And we came closer than we like to a blocklisting, which would have affected every email we send.

It could have been worse. The attack had a clear signature, the fix was small, and our mail infrastructure recovered on its own. But a small company's email reputation takes years to build and days to damage.

A checklist for anyone running a contact form

If your website sends an email to an address a visitor types in, this can happen to you. Here is what we would check, in order of how much it matters.

  1. Never repeat visitor input in an automatic reply. Not the name, not the message, not the subject. Use fixed wording. If the reassurance matters, show the visitor their message on the confirmation page instead, where only they can see it.
  2. Ask whether you need the automatic reply at all. A clear confirmation page does most of the same job and sends nothing to anyone. RFC 3834, which sets out good practice for automatic email responses, is a useful read here.
  3. Limit automatic replies across the whole site, not only per visitor. Attackers rotate addresses. A site-wide ceiling is the only limit a distributed bot cannot spread its way around.
  4. Put a challenge before anything that sends email. It does not need to be elaborate. It needs to cost a bot something, expire, and not be reusable.
  5. Give your website its own sending address with its own limit. Do not let a web form send from a person's mailbox, and set a ceiling on the address it does use at the mail server, where the website cannot override it.
  6. Watch your outbound volume. Our form went from about four emails a week to 100 an hour. A simple alert on that jump would have caught the attack in its first hour instead of its fourth day.
  7. Treat delay and bounce notices as information. They are the mail system telling you what your server is doing. Read a few; parse them if there are many. The "Action" field separates delays from real failures.
  8. Read your DMARC reports, but know what they prove. They confirm your mail is authenticated. They cannot tell you whether the content was yours.
  9. Record where each submission really came from. Log the address of the connection your server received, alongside anything the form reports, so you can look at the sources afterwards.
  10. Have a way to switch off the automatic reply in a hurry. A single setting that stops the acknowledgement, without taking the form down, turns a four-day incident into a one-hour one.

None of this is new, and much of it is in the major mailbox providers' published guidance for senders. We knew most of it. We had simply not looked at our own contact form with an attacker's eyes, because it was the least interesting thing on our website. That turned out to be exactly why it was worth attacking.

Sources

Categories & features