{"id":66,"date":"2026-10-04T21:10:00","date_gmt":"2026-10-04T18:10:00","guid":{"rendered":"https:\/\/fsaeed.blog\/?p=66"},"modified":"2026-10-31T00:18:50","modified_gmt":"2026-10-30T21:18:50","slug":"stop-phishing-with-rspamd","status":"publish","type":"post","link":"https:\/\/fsaeed.blog\/index.php\/2026\/10\/04\/stop-phishing-with-rspamd\/","title":{"rendered":"Stopping Phishing at the Mail Server with Rspamd"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Hello again, tech enthusiasts! \ud83d\udc4b<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Last week, a colleague got an email from &#8220;Example Bank Support&#8221;: &#8220;Payment advice attached.&#8221; The attachment was a fake login page. Luckily, they asked before clicking. Next time, someone may not ask.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 <strong>Rspamd<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What you need<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A mail server that uses Rspamd (many ready mail server packages include it)<\/li>\n\n\n<li>Shell access to the server, with <code>sudo<\/code> rights<\/li>\n\n\n<li>One saved phishing email as a <code>.eml<\/code> file for testing (most email apps can &#8220;save as&#8221; or &#8220;download original&#8221;)<\/li>\n\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Understand scores and actions<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Rspamd is a spam filter. It runs many small tests on every message. Each test that matches is a <strong>symbol<\/strong> and adds points, like a guard&#8217;s checklist: &#8220;wrong ID card, +3 points&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the end, the total score decides the <strong>action<\/strong>. The default limits are roughly:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>greylist<\/strong> at 4 points: ask the sender server to try again later<\/li>\n\n\n<li><strong>add header<\/strong> at 6 points: deliver, but mark it as spam (usually it goes to the Junk folder)<\/li>\n\n\n<li><strong>reject<\/strong> at 15 points: refuse the message<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You can change these in <code>\/etc\/rspamd\/local.d\/actions.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>greylist = 4;\nadd_header = 6;\nreject = 15;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Always put your settings in <code>local.d<\/code>. Updates replace the main files.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Know the usual tricks<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Most phishing emails use the same few tricks:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Fake display name.<\/strong> The name says &#8220;Example Bank&#8221;, but the address is <code>support@random-domain.test<\/code>. Phones often show only the name!<\/li>\n\n\n<li><strong>Fake payment files.<\/strong> Attachments called &#8220;Payment Advice.html&#8221; or &#8220;Remittance.zip&#8221;. HTML files can show fake login pages.<\/li>\n\n\n<li><strong>Look-alike domains.<\/strong> <code>examp1e.com<\/code> instead of <code>example.com<\/code>.<\/li>\n\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Rspamd catches many of these already, but you can add rules for the brands that matter to <strong>you<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Block a sender domain<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To block a domain that keeps sending fake emails, we use the <strong>multimap<\/strong> module. A map is a simple list in a text file. Create <code>\/etc\/rspamd\/local.d\/multimap.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BLOCKED_SENDER_DOMAIN {\n  type = \"from\";\n  filter = \"email:domain\";\n  map = \"${LOCAL_CONFDIR}\/local.d\/maps.d\/blocked_domains.map\";\n  action = \"reject\";\n  message = \"Sender domain is blocked\";\n  description = \"Sender domain is on our block list\";\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then create the list, one domain per line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/etc\/rspamd\/local.d\/maps.d\necho \"bad-sender.test\" | sudo tee \/etc\/rspamd\/local.d\/maps.d\/blocked_domains.map<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Catch a brand name from the wrong domain<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now we want: &#8220;the name says Example Bank, <strong>but<\/strong> the domain is not examplebank.com&#8221;. We make two map rules, and then join them with a <strong>composite<\/strong>. A composite is a rule made from other rules, like &#8220;A and not B&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Add these to the same <code>multimap.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BRAND_IN_DISPLAY_NAME {\n  type = \"from\";\n  filter = \"email:name\";\n  map = \"${LOCAL_CONFDIR}\/local.d\/maps.d\/brand_names.map\";\n  regexp = true;\n  score = 0.0;\n  description = \"Display name contains a protected brand\";\n}\n\nBRAND_REAL_DOMAIN {\n  type = \"from\";\n  filter = \"email:domain\";\n  map = \"${LOCAL_CONFDIR}\/local.d\/maps.d\/brand_domains.map\";\n  score = 0.0;\n  description = \"Sender is from the real brand domain\";\n}\n\nPAYMENT_ADVICE_FILE {\n  type = \"filename\";\n  map = \"${LOCAL_CONFDIR}\/local.d\/maps.d\/bad_filenames.map\";\n  regexp = true;\n  score = 6.0;\n  description = \"Attachment looks like a fake payment advice\";\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The three map files:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># brand_names.map (regular expressions, \/i = ignore upper\/lower case)\n\/example\\s*bank\/i\n\/example\\s*pay\/i\n\n# brand_domains.map (the real domains)\nexamplebank.com\nexamplepay.com\n\n# bad_filenames.map\n\/(payment|remittance).*\\.(html?|zip|iso|img)$\/i<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now create <code>\/etc\/rspamd\/local.d\/composites.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BRAND_SPOOF {\n  expression = \"BRAND_IN_DISPLAY_NAME &amp; !BRAND_REAL_DOMAIN\";\n  score = 10.0;\n  description = \"Brand name in display name, but from another domain\";\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">10 points sends the message to Junk. With other bad signs, it goes over 15 and is rejected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check the config and reload:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo rspamadm configtest\nsudo systemctl reload rspamd<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 5: Test with a saved email<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Test with your saved <code>.eml<\/code> file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rspamc symbols saved-phishing.eml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You will see the score, the action, and every matching symbol. Look for <code>BRAND_SPOOF<\/code>. Then test a <strong>real<\/strong> email from the brand, to make sure it passes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 6: Train with spam and ham<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Rspamd can also learn. Its Bayes filter learns which words appear in spam and which appear in good mail (&#8220;ham&#8221;). Give it examples from folders of saved messages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rspamc learn_spam \/path\/to\/spam-folder\/\nrspamc learn_ham \/path\/to\/good-mail-folder\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Common problems<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>configtest<\/code> fails.<\/strong> Look for a missing <code>;<\/code> or <code>}<\/code> in your files. The error message shows the line.<\/li>\n\n\n<li><strong>The rule never matches.<\/strong> Check that the map file path is correct and readable by Rspamd.<\/li>\n\n\n<li><strong>Real brand emails go to Junk.<\/strong> The brand uses another domain for emails, like <code>mail.examplebank.com<\/code>. Add it to <code>brand_domains.map<\/code>.<\/li>\n\n\n<li><strong><code>rspamc<\/code> not found (Docker setups).<\/strong> Run it inside the Rspamd container.<\/li>\n\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Wrap-up<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now your server knows your brands, blocks bad domains, and catches fake payment files before anyone can click. \ud83d\udee1\ufe0f<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Use Rspamd scores, maps and training to catch fake brand emails before they reach your team.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[15,27,68,67,32],"class_list":["post-66","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-email","tag-linux","tag-phishing","tag-rspamd","tag-security"],"_links":{"self":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts\/66","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/comments?post=66"}],"version-history":[{"count":1,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts\/66\/revisions"}],"predecessor-version":[{"id":67,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts\/66\/revisions\/67"}],"wp:attachment":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/media?parent=66"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/categories?post=66"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/tags?post=66"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}