senn-techsenn-tech
← All references03 · IT

A mail gateway we run ourselves

Reference client: Logistics and trading group in Tyrol, Austria · ~100 employees · 4 sites

Customer data in copy and images has been neutralised.

92.000inbound messages in 30 days
11 %of them rejected at the gateway
2virus scanners, both must report clean
01

Starting point

The group's mail gateway used to be a vendor's Windows software. We wanted to see what happens to every message and to be able to change the behaviour ourselves.

Since 23 September 2026 the gateway has run on our own Linux system, built by us. It accepts the mail, checks it and passes on only what has been checked.

02

The hard part

A mail filter can be wrong in two ways, and both cost money. If it lets malware through, there is an incident. If it holds back a genuine customer order, there is a phone call. When in doubt the gateway has to hold, but in a way that lets a person release the message within minutes and never loses one.

We also set ourselves a rule: protection functions and their display run inside the app itself. Only the AI scoring and the DNS blocklists come from outside.

03

Solution

Postfix accepts the mail, a Python service scores it, a PostgreSQL database holds rules, log and the held messages, and our own interface shows all of it.

Two virus scanners check every attachment, ClamAV and ESET, and both must report clean. If a scanner gives no verdict, the gateway rejects the message with a temporary error (451). It is retried later, nothing passes unchecked.

Suspicious messages go to a hold store, and a person releases or deletes them. An AI classifier scores along but decides nothing yet: it runs in shadow mode until its hit rate is proven.

PostfixPython (pymilter)PostgreSQLClamAVESETCrowdSec
04

How it is built

Every message passes a fixed sequence: acceptance, greylisting, virus scan, spam scoring, rules. The end result is one of three decisions: deliver, hold or reject. Every decision is logged with its reason.

Zip files stay on the message if the sender rule allows it and the zip and every file inside is clean at both scanners. Otherwise the attachment sits in the portal, and the message carries a notice file with the queue ID. A timer reports every removed attachment to the administrators every five minutes.

Every delivered message also goes as a journal copy to two archives. The journal is mirrored into the ERP database every hour.

The gateway queries the DNS blocklists itself and shows size and response time per list in the interface. A list counts as active only once it has its own probe in the app.

Abb. 01The path of a message: five steps, three possible outcomes. Below, the figures for the 30 days to 7 October 2026.
Five steps of an inbound message with the figures of the last 30 days

Abb. 01The path of a message: five steps, three possible outcomes. Below, the figures for the 30 days to 7 October 2026.

05

Day-to-day operation

A self-check runs every 15 minutes. On a new deviation or a critical finding, a mail goes to the administrators.

Updates run in a fixed evening window. Before anything is rolled out, the running version must be contained in the new branch, otherwise the script aborts. After every database restart Postfix reloads its tables, because otherwise dead connections remain.

Abb. 02When in doubt: held, deferred with a temporary error or the attachment moved to the portal. Nothing passes unchecked.
Three cases in which the gateway holds, defers or sets aside a message

Abb. 02When in doubt: held, deferred with a temporary error or the attachment moved to the portal. Nothing passes unchecked.

06

Outcome

In the 30 days to 7 October 2026, 91,950 messages arrived from outside. The gateway rejected 10,108, which is 11 percent. It held 1,267 messages, 51 of them for malware. A person released 87 and deleted 1,056.

Measured in the gateway's message database, as of 7 October 2026.

07

What we learned

The AI classifier stays in shadow mode. On text alone it reached a separation (AUC) of 0.889 on our held messages, which does not protect genuine orders reliably enough. With metadata and our own model on the CPU it reaches 0.971. By 18 October 2026 we decide whether to switch it on.

Similar problem?

Tell us what you're planning, a short call clarifies whether it pays off.