---
summary: 'Running scailine-milter next to Postfix: enrolment, Postfix settings, what
  it changes, and how to switch it off.'
order: 1
title: Postfix and the milter
path: operating/postfix-milter
status: published
---

# Postfix and the milter

`scailine-milter` runs on your mail server and signs outgoing mail as Postfix passes it along.
Message content never leaves the server. The [Quickstart](quickstart) walks through a first
installation; this page is the reference.

## Enrolment

Each server is a **milter node**, enrolled under **Milter nodes** in the console. Enrolment
returns a token (`smn_…`) once; the console stores only a hash of it. Revoke a node there when a
server is retired or the token may have leaked: the node stops receiving updates immediately and
keeps signing with its last copy until you stop it.

## Configuration

`/etc/scailine/.env`, readable only by root and the milter's group:

| Setting | Meaning |
|---|---|
| `MILTER_API_BASE_URL` | Where snapshots come from (`https://scailine.scailabs.ai`). |
| `MILTER_ENROLMENT_TOKEN` | The node token from enrolment. |
| `MILTER_SNAPSHOT_PUBLIC_KEY` | Public key that snapshots must be signed with. Without it nothing is loaded and mail passes unsigned. |
| `MILTER_SOCKET` | Unix socket Postfix connects to, e.g. `/var/spool/postfix/scailine/milter.sock`. |
| `MILTER_SNAPSHOT_DIR` | Where the last good snapshot is kept (`/var/lib/scailine-milter`, private to the milter). |
| `MILTER_MAX_MESSAGE_BYTES` | Larger messages pass unsigned. Default 25 MiB. |

The snapshot contains your organisation's compiled signatures and the key used to sign the
`X-ScaiLine` header, which is why its directory must be private to the milter user.

## Postfix

Sign **outgoing mail only**:

| Setting | Value | Why |
|---|---|---|
| `non_smtpd_milters` | `unix:scailine/milter.sock` | Local submission (`sendmail`, pickup): grommunio's outgoing path and system mail. |
| `smtpd_milters` on the `submission` service (`master.cf`, `-o`) | `unix:scailine/milter.sock` | Authenticated clients sending on port 587. |
| `smtpd_milters` for port 25 | empty | Incoming mail must never be signed. |
| `milter_default_action` | `accept` | If the milter is down or slow, mail is delivered unsigned instead of held. |
| `milter_protocol` | `6` | |
| `milter_connect_timeout`, `milter_command_timeout`, `milter_content_timeout` | `5s`, `15s`, `30s` | Bound the worst-case delay. |

The socket path is relative to Postfix's queue directory, so it also works when Postfix runs
chrooted. Postfix must be able to open the socket: put the `postfix` user in the milter's group.

Keep a copy of `main.cf` and `master.cf` before changing them; restoring them and running
`postfix reload` removes ScaiLine from the mail path entirely.

## What the milter changes

* The message body, where the signature is inserted, and the embedded images if the template
  uses them.
* One added `X-ScaiLine` header.
* For a message that consists only of an HTML part, its top-level `Content-Type` (it becomes
  `multipart/related` when images are embedded).

Nothing else. If a change would require touching any other header, the message passes
unmodified.

## Snapshots

The milter asks for a new snapshot every 60 seconds and gets "no change" most of the time.
A snapshot with a bad signature, a network error or a server error never replaces the copy on
disk. After a restart the milter starts from the copy on disk, so it signs mail even when the
control plane cannot be reached.

## Operations

* Status: `systemctl status scailine-milter`.
* Logs go to ScaiLog; they never contain message content, and senders are pseudonymised.
* Processing counts per day (signed, skipped, passed through) are reported to ScaiLine as
  statistics; they contain no message data.
* To switch signing off: `systemctl stop scailine-milter`. Postfix keeps delivering (with
  `milter_default_action = accept`), without signatures.
