Varia

... from your experts on pretty much everything.


Mastodon as a mail relay: not the victim, the weapon

For eight days, criminals used my instance to deliver confirmation emails to other people's mailboxes. How I stopped it, and what we should take away from it.

This article in Deutsch

Contents

Between September 10 and September 17, 2026, my Mastodon server troet.fediverse.at sent 496 confirmation emails to addresses that had never asked for them. To get there, 340 accounts were created on my server, each from a different IP address. Those confirmation emails, which the server sends automatically on sign-up, were the attackers’ actual objective. A service called SendFlood sells such “mail bombs” and charges per message that verifiably lands in the inbox of a victim its customer has picked. In that “business” process, we in the Fediverse are not the victim — we are the attacker’s weapon.

I am writing this up because it took me a while to get from the symptom (“a flood of mail”) to an understanding of what sat behind it — and because along the way I found things that affect every Mastodon instance and some PeerTube instances, not just mine.

What the logs say

My first guess: bots are filling in the sign-up form. That was wrong. Going through the nginx logs from September 9 to September 17 shows a different picture:

Path Requests Result
POST /auth (web form) 6 real people, from a single IP
POST /api/v1/accounts (API) 565 340 with HTTP 200 = 340 accounts = 340 emails
POST /auth/confirmation (“resend”) 156 156 further emails

Six requests to the form, 565 to the API. The route runs through POST /api/v1/apps — an endpoint Mastodon leaves open to anyone, without authentication, so that third-party apps can register themselves. You register an app there, use its credentials to fetch a client-credentials token, and send POST /api/v1/accounts with it. The account is done, and the confirmation email is on its way.

337 of the 340 accounts followed the pattern bp plus 16 hexadecimal characters. That is also the name the wave goes by in the Fediverse: bp spam.

One thing followed from this: the measure I had originally planned — an arithmetic puzzle in front of the sign-up form — would not even have touched this attack.

The confirmation emails are the objective

The decisive hint did not come from my logs but from another admin. On September 17, @zoul@boskovice.social received a registration carrying the justification “Registration-mail deliverability check for the sendflood.com compatibility matrix; recipient is the operator-approved own mailbox”, and posted the wording to the Fediverse. The second clause tells you how the compatibility matrix comes about: the service first tests an instance’s mail delivery against a mailbox of its own before taking it into the catalogue.

The offering — “Trigger verification email at scale”: the counter at the top gives the number of sites for which the service can trigger mail delivery. The page is deliberately not linked here; the screenshot serves as evidence for the quotation.

SendFlood describes itself like this:

SendFlood sends protocol-level registration/resend requests to supported sites and verifies delivery by INBOX evidence. You pay only for successful triggers.

Home page of the service, screenshot taken on 18 September 2026

The resend path is interesting because the email can be re-triggered as often as you like for every account created. My 340 accounts plus 156 resends make up the roughly 496 emails — from one instance. Billing is per successful hit, paid in USDT, and what counts as proof is not the email sent but the email delivered. The counter at the top of the image is worth noting. On September 17 and 18 the home page gave 4,918 supported sites; when I fetched it on September 18 it said 6,316 — and on the screenshot, taken a few minutes later, 6,318. They are at least making it look as though they are continuously adding new mail relays — how many of those are Mastodon servers we of course do not know.

Going the long way round through other people’s sign-up forms is not clumsiness, it is the core of the business model:

  • The emails come from real, reputable senders with valid SPF, DKIM and DMARC. They land in the inbox, not in the spam folder. A flood in the spam folder would be worthless for the purpose.
  • They cannot be filtered without blocking genuine confirmation emails along with them. Taken on its own, every single one is a legitimate email from a different service.
  • The attacker needs no mail infrastructure of his own, no IP reputation, no budget. We supply all of that.
  • Attribution points at thousands of uninvolved third parties. Anyone who investigates ends up at me, not at him.

What is mail bombing for?

A flood of mail is rarely an end in itself. It is usually background noise meant to drown out something else. Several purposes are documented:

Hiding fraud alerts. After an account takeover or card misuse, the perpetrator floods the victim’s mailbox so that the bank’s warning or a purchase confirmation disappears among thousands of confirmation emails. A few hours of delay are enough to move the money on. The security vendor BlackCloak coined the term registration bomb for this variant in 2022; in the case it described, a purchase confirmation from Walmart.com vanished under more than 500 registration emails.

A precursor to ransomware. First an employee’s mailbox is flooded, then somebody calls, poses as IT support wanting to “fix the spam problem”, and gets granted remote access. Microsoft observed this approach in 2024 with the group Storm-1811.

Intimidation. Journalists, activists, ex-partners. Brian Krebs Brian Krebs is an investigative journalist, best known for his reporting on cybercriminals. was on the receiving end himself in 2016: one email every two to three seconds, his Gmail account unusable for a whole weekend.

Paralysing role mailboxes. abuse@, support@, a complaints desk, a reporting address. For a small organisation, that is communication over and done with.

Which of these applies in my case I of course do not know — I only see the addresses, not what else happens to them. The distribution is striking, though: 115 of the recipients had an address at docomo.ne.jp, joined by ezweb.ne.jp and further Japanese mobile domains. These are Japanese mobile mail addresses, tied to the phone number and traditionally the address there for bank notifications, carrier billing and second factors. If somebody floods those mailboxes in particular, that fits the first motive better than the others. From where I sit, that is no more than a guess.

Why filters don’t work

Countermeasures have been circulating in the Fediverse for weeks: block the app name, block the username prefix, block the mail domain, block the user agent. My own oauth_applications table documents what becomes of that. Since September 9, 597 applications have been created there, all without an owner — so all of them through the open endpoint:

Stage Application name Period Count
1 BoomProtocolProbe Sep 9, 07:46 to Sep 16, 20:59 464
2 sf-probe- + 8 hex Sep 16, 18:23 to 20:33 23
3 Mastodon Client, Mastodon for Web, mastodon, Mastodon Web, Web, Mastodon, Fediverse, Mastodon Web App Sep 16, 21:07 to Sep 17, 21:10 110

Stage 3 is the point that matters: these are the names under which genuine clients register too. My table has a legitimate Web from 2022 sitting right next to them. A name block is not merely circumvented at that point — it can no longer be formulated without blocking real clients along with it.

The same goes for every other variable. Up to September 16 the user agent was Python/3.10 aiohttp, after that a forged Chrome. Alongside bp, the username prefix also showed up as sa and ap. Every instance that drops out costs the operator revenue — so he adapts. That is the structural reason why any string filter is dead within days.

Incidentally: the last of these applications was created on September 17 at 21:10, which is after I had closed registration. They keep trying.

A flaw in Mastodon’s code?

The question that interested me most as an admin: why does not a single built-in measure take hold? The answer sits in the source of Mastodon 4.7.2.

Api::V1::AccountsController#create calls AppSignUpService. That checks allowed_registration? — single-user mode, registrations_mode, IP blocks and invite code. No captcha hook, no honeypot. The web form at least has honeypot fields and a minimum time for filling it in; the RegistrationFormTimeValidator does run on the API path as well, but without a form time it has no effect.

Three points that could be addressed:

  1. hCaptcha sits in the wrong place. Mastodon hangs it in the Auth::ConfirmationsController, that is, after the mail has gone out. It protects against a bot obtaining a usable account — not against a bot triggering an email. For a service that gets paid per email, that is the difference between ineffective and effective.
  2. Requiring approval does not help. The record is created, Devise Devise is a widely used software library that takes care of sign-in matters for Rails applications: registration, login, password reset and confirming the email address. So Mastodon does not write this part itself. sends immediately, and moderation is only notified after the recipient has confirmed (after_confirmation_tasks). The email is long gone before anybody looks at it.
  3. There is no global ceiling. All of the Rack::Attack throttles are per IP, per account or per address. The attacker had his own IP for every account. That left every single throttle running into the void.

On top of that there is a path I consider worse than the one that was used: POST /api/v1/emails/confirmations does current_user.update!(email: params[:email]) and then sends afresh — to a freely chosen address. Within the existing limit of five requests per 30 minutes, a single unconfirmed account created via the API can thereby trigger 240 emails a day to arbitrary recipients. And accounts are free. That my attacker did not use this was luck. The 240 is the lower bound. According to Devise’s source, changing the address already sends a confirmation email, and the subsequent resend triggers a second one. I have not tried it out.

One more point for anyone about to close registration in a hurry: allowed_registration? lets a sign-up through even with registrations_mode = none as soon as a valid invite code comes with it — and POST /api/v1/accounts accepts invite_code. Closed registration is only as tight as the invitations lying around. In my case two codes from 2023 with no expiry were still there, neither ever used.

A solved problem — just not in Mastodon

What a solution can look like was something I could check on my own doorstep. Alongside the Mastodon instance I run tube.fediverse.at, a PeerTube instance. It caught the same wave, on September 9 and 10: 16 registration requests, usernames following the pattern bp plus 10 hexadecimal characters, the same Japanese mobile addresses — and for all 16 the same justification text, “Automated protocol deliverability probe”. That is the same choice of words that turns up on the Mastodon side as the app name BoomProtocolProbe.

There it stopped at around 20 emails instead of 496. Mail confirmation is switched on for tube as well, so one email went out per request. That it stayed at so few was not down to the software: on tube the attacker only used three IP addresses, which is why PeerTube’s built-in limit of two sign-ups per five minutes and IP actually bit — four of the 24 calls to POST /api/v1/users/register ended in a 429. The same design — a limit per IP — achieved nothing on the Mastodon side, because there every sign-up came from an address of its own.

The difference that matters is a different one. In PeerTube, mail confirmation is an option, not a fixed stage. With signup.requires_email_verification: false and signup.requires_approval: true, a registration request triggers no email to the applicant at all — and those, incidentally, are the values PeerTube itself ships in its example configuration. That is how PeerTube’s registration controller reads; on tube I have not switched this over yet. The request lands in the moderation queue; only once the admin accepts it does a single email go out — to an address he has looked at beforehand. That makes the instance worthless as a mail relay, regardless of how many IP addresses somebody rents and what strings he writes into the request.

In Mastodon, by contrast, confirmation is Devise :confirmable and hard-wired. The only adjustment available is registrations_mode, and that knows only open, with approval, or closed. In approval mode it does not prevent the email.

The pattern is not new anyway. The newsletter industry took its share of “subscription bombing” about ten years ago. In August 2016 a wave hit more than 100 government addresses in various countries, which were entered into large numbers of mailing lists within a short time; a single sender found fewer than ten addresses on its own systems that were signed up to almost 10,000 of its lists.

At the time, Spamhaus explicitly put the attack’s success down to how many newsletters were skipping the basic step of having a sign-up confirmed. The bill for that went not to the attacker but to the senders: Spamhaus instructed providers to block mail from some of the largest email service providers, and their customers could no longer get their marketing mail through. That is the same mechanism now gnawing at the mail reputation of Fediverse instances.

The conclusion Spamhaus drew in September 2016 is the same one missing here: a captcha on the sign-up form, that is, before the mail goes out, is “the single best thing” you can do to secure a form. Double opt-in on its own is not enough, because the sheer volume of confirmation emails is itself the problem. Large platforms handle it the same way, as far as I know: the human-or-machine check sits at the point of submitting the form.

Two differences still make the Fediverse the more rewarding target. First, the open, unauthenticated accounts API — which commercial platforms, as far as I know, do not offer. It is documented, stable, and identical across thousands of instances. That is the precondition for anybody being able to build a product with a compatibility matrix out of it; public instance directories supply the target list along with it. Second, there is no central defence. At a large provider, one team sees the pattern and fixes it globally. Here, thousands of admins work individually against the same opponent.

What I did

My goal was narrowly drawn: registration should be open again, with approval by me as before. Abuse as a mail sender should no longer be possible. I put this in place on September 18.

API registration is switched off in nginx. The web server answers POST /api/v1/accounts and POST /api/v1/emails/confirmations with a 403 and a note in plain language. This happens before Rails, so nothing gets created and nothing gets sent. One thing matters here: the block hits only the path itself, so that /api/v1/accounts/:id/follow, verify_credentials and all the rest remain untouched — and every location is entered twice, with and without a trailing slash. Rails maps both spellings to the same route; nginx does not. Without the second variant, the block could be bypassed with a slash.

I deliberately did not block POST /api/v1/apps. That would break login for all third-party apps. Without the route through /api/v1/accounts, app registration there is worthless anyway.

403 in the app — Registration attempt from Mastodon for iOS. The app does display the text of the 403 response — which was to be expected, hence the note is phrased in plain language and gives the web address. The missing umlauts come from the nginx configuration.

Tealk built the same workaround with a different tool, crowdsec: https://git.rollenspiel.monster/ansible-playbooks/proxymanager/commit/2ebe0a46c81092bd379b7925385b86c79b82a034

A global ceiling on sign-up emails. The remaining web routes (POST /auth and POST /auth/confirmation) now run through a limit_req zone with a constant key — not per IP, but for all of them together. What this ceiling achieves is limited, though: 1r/m is the finest rate nginx knows, which is 60 emails an hour. My incident ran at roughly 2.6 emails an hour. This ceiling would not have reduced this attack by a single email. It is a damage limit for the case where somebody switches to the web form and drives volume through it. A real hourly ceiling would only be possible inside Mastodon itself.

Monitoring. Until now I would not have noticed the 340 accounts until the flood of mail — which is exactly how it went. Since then a Nagios check counts every quarter of an hour how many users were sent a confirmation email in the past hour (confirmation_sent_at). This metric captures every mail route, including the resend, and also one I have not thought of. Warning at 5, critical at 20.

Cleaned up. The OAuth applications left behind since September 9 have been deleted, 597 of them along with their access tokens, by a criterion with no reference to names whatsoever: created since September 9, without an owner, and no token pointing at an existing user. That is hygiene, not a security gain — the attacker fetches a new token with a single request. But the table is readable again.

Registration open again, in the mode with approval by me.

Drawbacks of my workaround

Registration directly from an app no longer exists on my instance. Of roughly 40 genuine accounts, nine had come about from apps, eight via Mastodon for Android, one via iOS. That is a good 20 percent.

There are two things I cannot switch off here. GET /api/v2/instance still reports that registrations are possible — so apps display the button, and you only end up at the error message when you submit. Without intervening in Mastodon’s source, that cannot be changed. And anybody who does not read the error message gives up. A note in the server description may help against that.

In fairness: with approval required, the app route was half-baked anyway — the app then waits for a clearance that only comes after my manual decision. And the UX of account registration via the Mastodon app is dire to begin with.

My workaround could be undone in a minute, though: comment out one include line, reload nginx.

What a durable fix would take

Everything I have done is a stopgap on my own server. The problem sits one level up, and as far as I know it is unsolved there.

Three routes would be conceivable in Mastodon, and one of them would be enough:

  • a setting that allows registration via the API to be switched off without involving the reverse proxy,
  • a captcha hook in AppSignUpService, that is, at the same place every commercial platform has one,
  • or, in the mode requiring approval, sending the confirmation email only after clearance.

The third route is the best one, for a practical reason: it is the only one that preserves registration from within the app. The model for it: PeerTube.

Where the lever would sit

In app/models/user.rb, all the mails Devise sends (confirmation, password reset, address change) run through a single method — addressing it there covers the web form, the API route and the resend all at once. Clearing an as yet unconfirmed account sends nothing today; that would be the place where the mail delivery (confirmation to the newcomer) would have to be made up for.

Whether a change in this form would work, I do not know — I am not enough of a developer to judge that.

Potential problems with this fix

  • The flood moves from the mail to the database. Without the nginx block, the bots go on creating accounts, just silently. For Postgres that is nothing, but every record occupies a name until somebody deletes it.
  • The interface would then say something false. Mastodon’s sign-up form sends you off to “check your email inbox”, and then nothing arrives for hours. PeerTube’s interface knows its mode and can therefore communicate it. An additional note would be needed.
  • I would be releasing addresses that are not verified. I consider that bearable — nobody can sign in regardless before they have received the mail and clicked it.

Upstream

There is an open issue about this wave, #40453 “feature: Block oauth applications”. Its proposal is itself another name filter, though; the author writes “I bet most spammers would change the name”, and that has since come to pass. An issue about the actual root cause — the mail goes out before any check can run — I did not find there, and I have not filed one myself so far either. Three open issues circle around it:

  • #33696 describes my attacker’s route step by step: create an OAuth app, fetch a client-credentials token, POST /api/v1/accounts. The point there is that this works even when registration is actually redirected to an external sign-in service. Open since January 2025.
  • #36435 proposes that apps open a web view for registration instead of sending POST /api/v1/accounts themselves — the way signing in via OAuth already works today. It was written up for entirely different reasons, namely that the app otherwise gets hold of the user’s password, that passkeys do not work, and that every app would have to collect age verification itself. For me it would have a pleasant side effect: were it implemented, my block would no longer cost me registration from within the app. Open since October 2025.
  • #39455 asks for a human-or-machine check at sign-up. The proposal itself is idiosyncratic — a video of your own hand — but the need behind it is on the mark.

That the building blocks are lying around separately and have been open for months is not a good sign.

Until there is a fix, this holds for admins: closing registration works, and works immediately. But check whether open invite codes are still lying around — those defeat it. And anyone running a PeerTube instance has it easier: requires_email_verification: false together with requires_approval: true takes the ground out from under the attack.

Conclusion

It is tempting to see all this as no more than an annoyance for Fediverse admins: full mailbox, registration closed, a nuisance. The real problem is far bigger. “My” 496 emails landed in mailboxes, possibly in order to obscure a fraud warning. A Fediverse server left open is, in this constellation, not the victim deserving sympathy — it is the criminals’ instrument.


Sources

zoul. The strange #bpspam saga continues Post on boskovice.social, 17 September 2026. 🌐

Ingo Lantschner. Discussion with other instance admins Thread on troet.fediverse.at, September 2026, in German. 🌐

SendFlood. Trigger verification email at scale. The service's own marketing, home page, screenshot taken 18 September 2026. Deliberately not linked.

mastodon/mastodon. feature: Block oauth applications Issue #40453, GitHub. 🌐

BlackCloak. New Registration Bomb Email Attack Distracts Victims of Financial Fraud Vendor report, syndicated on Security Boulevard, 1 March 2022. 🌐

Brian Krebs. Massive Email Bombs Target .Gov Addresses KrebsOnSecurity, August 2016. 🌐

PeerTube. config/default.yaml Defaults for signup.requires_approval and signup.requires_email_verification, project repository. 🌐

PeerTube. server/core/controllers/api/users/registrations.ts Registration controller: mail to the applicant only with requires_email_verification, one mail on acceptance (v8.3.0). 🌐

mastodon/mastodon. Source code v4.7.2 app/services/app_sign_up_service.rb, app/helpers/registration_helper.rb, app/controllers/api/v1/emails/confirmations_controller.rb, app/controllers/auth/confirmations_controller.rb, app/models/user.rb, config/initializers/rack_attack.rb. 🌐

Spamhaus. Subscription Bombing: COI, CAPTCHA, and the Next Generation of Mail Bombs Blog of the Spamhaus Project, 16 September 2016. 🌐

Microsoft. Threat actors misusing Quick Assist in social engineering attacks leading to ransomware Microsoft Security Blog, 15 May 2024 (Storm-1811). 🌐