Stopping Phishing at the Mail Server with Rspamd


Hello again, tech enthusiasts! 👋

Last week, a colleague got an email from “Example Bank Support”: “Payment advice attached.” The attachment was a fake login page. Luckily, they asked before clicking. Next time, someone may not ask.

Phishing is when attackers pretend to be someone you trust, to get your password or money. Training helps, but it is better if the fake email never arrives. Today we stop it at the mail server with Rspamd.

What you need

  • A mail server that uses Rspamd (many ready mail server packages include it)
  • Shell access to the server, with sudo rights
  • One saved phishing email as a .eml file for testing (most email apps can “save as” or “download original”)

Step 1: Understand scores and actions

Rspamd is a spam filter. It runs many small tests on every message. Each test that matches is a symbol and adds points, like a guard’s checklist: “wrong ID card, +3 points”.

At the end, the total score decides the action. The default limits are roughly:

  • greylist at 4 points: ask the sender server to try again later
  • add header at 6 points: deliver, but mark it as spam (usually it goes to the Junk folder)
  • reject at 15 points: refuse the message

You can change these in /etc/rspamd/local.d/actions.conf:

greylist = 4;
add_header = 6;
reject = 15;

Always put your settings in local.d. Updates replace the main files.

Step 2: Know the usual tricks

Most phishing emails use the same few tricks:

  1. Fake display name. The name says “Example Bank”, but the address is support@random-domain.test. Phones often show only the name!
  2. Fake payment files. Attachments called “Payment Advice.html” or “Remittance.zip”. HTML files can show fake login pages.
  3. Look-alike domains. examp1e.com instead of example.com.

Rspamd catches many of these already, but you can add rules for the brands that matter to you.

Step 3: Block a sender domain

To block a domain that keeps sending fake emails, we use the multimap module. A map is a simple list in a text file. Create /etc/rspamd/local.d/multimap.conf:

BLOCKED_SENDER_DOMAIN {
  type = "from";
  filter = "email:domain";
  map = "${LOCAL_CONFDIR}/local.d/maps.d/blocked_domains.map";
  action = "reject";
  message = "Sender domain is blocked";
  description = "Sender domain is on our block list";
}

Then create the list, one domain per line:

sudo mkdir -p /etc/rspamd/local.d/maps.d
echo "bad-sender.test" | sudo tee /etc/rspamd/local.d/maps.d/blocked_domains.map

Step 4: Catch a brand name from the wrong domain

Now we want: “the name says Example Bank, but the domain is not examplebank.com”. We make two map rules, and then join them with a composite. A composite is a rule made from other rules, like “A and not B”.

Add these to the same multimap.conf:

BRAND_IN_DISPLAY_NAME {
  type = "from";
  filter = "email:name";
  map = "${LOCAL_CONFDIR}/local.d/maps.d/brand_names.map";
  regexp = true;
  score = 0.0;
  description = "Display name contains a protected brand";
}

BRAND_REAL_DOMAIN {
  type = "from";
  filter = "email:domain";
  map = "${LOCAL_CONFDIR}/local.d/maps.d/brand_domains.map";
  score = 0.0;
  description = "Sender is from the real brand domain";
}

PAYMENT_ADVICE_FILE {
  type = "filename";
  map = "${LOCAL_CONFDIR}/local.d/maps.d/bad_filenames.map";
  regexp = true;
  score = 6.0;
  description = "Attachment looks like a fake payment advice";
}

The three map files:

# brand_names.map (regular expressions, /i = ignore upper/lower case)
/example\s*bank/i
/example\s*pay/i

# brand_domains.map (the real domains)
examplebank.com
examplepay.com

# bad_filenames.map
/(payment|remittance).*\.(html?|zip|iso|img)$/i

Now create /etc/rspamd/local.d/composites.conf:

BRAND_SPOOF {
  expression = "BRAND_IN_DISPLAY_NAME & !BRAND_REAL_DOMAIN";
  score = 10.0;
  description = "Brand name in display name, but from another domain";
}

10 points sends the message to Junk. With other bad signs, it goes over 15 and is rejected.

Check the config and reload:

sudo rspamadm configtest
sudo systemctl reload rspamd

Step 5: Test with a saved email

Test with your saved .eml file:

rspamc symbols saved-phishing.eml

You will see the score, the action, and every matching symbol. Look for BRAND_SPOOF. Then test a real email from the brand, to make sure it passes.

Step 6: Train with spam and ham

Rspamd can also learn. Its Bayes filter learns which words appear in spam and which appear in good mail (“ham”). Give it examples from folders of saved messages:

rspamc learn_spam /path/to/spam-folder/
rspamc learn_ham /path/to/good-mail-folder/

By default it needs 200 examples of each before it starts to score. Many mail servers also learn when users move a message to Junk.

Common problems

  • configtest fails. Look for a missing ; or } in your files. The error message shows the line.
  • The rule never matches. Check that the map file path is correct and readable by Rspamd.
  • Real brand emails go to Junk. The brand uses another domain for emails, like mail.examplebank.com. Add it to brand_domains.map.
  • rspamc not found (Docker setups). Run it inside the Rspamd container.

Wrap-up

Now your server knows your brands, blocks bad domains, and catches fake payment files before anyone can click. 🛡️

Next idea: add the names of your own company and your main suppliers to the brand map. Attackers love to pretend to be the people you already pay.