DevOps · K8s · Volleyball · Travel  •  DevOps · K8s · Volleyball · Travel  •  DevOps · K8s · Volleyball · Travel
Explore NY Stream

How to Record All Incoming & Outgoing Mails To Seperate Email Addresses In Postfix

— ny_wk

How to Record All Incoming & Outgoing Mails To Seperate Email Addresses In Postfix

Postfix email archiving lets you silently copy every incoming and outgoing message to a dedicated journaling mailbox using the built-in recipient_bcc_maps and sender_bcc_maps features. The result: a tamper-evident, searchable archive of all mail flowing through your server, with no changes required on the client side.

The Problem: You Need a Complete Record of Every Email

Regulated industries, legal discovery, internal audits, and basic operational hygiene all share one requirement: keep a copy of every message that enters or leaves the organization. Relying on individual users to retain their own mail is hopeless. People delete messages, empty trash, and leave the company taking their mailboxes with them.

The clean solution is to make the mail server itself the system of record. Postfix email archiving via blind-carbon-copy (BCC) journaling does exactly that. The mail server transparently drops a copy of each message into an archive mailbox the moment it is delivered or relayed. Senders and recipients never see the BCC, and they cannot opt out.

The Solution: sender_bcc_maps and recipient_bcc_maps

Postfix ships with three native directives for this job, no plugins or external milters required:

  • recipient_bcc_maps — BCC a copy based on the recipient address. Catches mail arriving for your users (effectively incoming mail).
  • sender_bcc_maps — BCC a copy based on the sender address. Catches mail your users send (effectively outgoing mail).
  • always_bcc — a blunt instrument that BCCs every message regardless of sender or recipient. Useful when you want one combined archive and do not need separate in/out boxes.

The strategy below uses two lookup tables so incoming and outgoing copies land in separate mailboxes (inmails and outmails), which makes auditing and search far easier than one giant pile.

A note on terminology. A common misconception is that recipient_bcc_maps means outbound and sender_bcc_maps means inbound. It is the reverse. Postfix keys these maps on the message's envelope sender and envelope recipient, so to capture everything addressed to your domain you key the recipient map on your domain, and to capture everything sent from your domain you key the sender map on your domain.

Prerequisites

  • A working Postfix installation. On RHEL/CentOS/Rocky/AlmaLinux it is usually the default MTA; on Debian/Ubuntu install it with apt.
  • Functional DNS so the server can resolve and accept mail for your domain (correct MX and A records, and a resolvable hostname).
  • Root or sudo access on the mail host.
  • A defined domain — this guide uses example.com and the hostname mail.example.com; substitute your own.

Step 1 — Install Postfix (if needed)

Confirm Postfix is present, and install it if it is not.

  • RHEL / CentOS / Rocky / AlmaLinux / Fedora: sudo dnf install postfix -y (older systems used yum install postfix -y).
  • Debian / Ubuntu: sudo apt update && sudo apt install postfix -y (choose “Internet Site” when prompted).

Check the version and that the binary is in place with postconf mail_version.

Step 2 — Create the Archive Mailboxes

Create two dedicated, low-privilege local users to hold the archives. Keeping them separate from real accounts means the journals are not casually browsed or deleted.

  1. Create the users: sudo useradd inmails and sudo useradd outmails.
  2. Set passwords. The classic RHEL one-liner is echo "StrongPasswordHere" | sudo passwd --stdin inmails. On Debian/Ubuntu, which lack --stdin, use sudo passwd inmails interactively, or echo "inmails:StrongPasswordHere" | sudo chpasswd.

Security tip: use long random passwords and, ideally, set the login shell to /usr/sbin/nologin so these accounts cannot be used for interactive logins — mail still gets delivered to their mailboxes regardless of shell.

Step 3 — Build the BCC Lookup Tables

Each table maps an address pattern to the archive mailbox that should receive the copy. Use a tab or spaces between the key and the value.

Create /etc/postfix/recipient_bcc (incoming — matches anything addressed to your domain):

@example.com    inmails@example.com

Create /etc/postfix/sender_bcc (outgoing — matches anything sent from your domain):

@example.com    outmails@example.com

The leading @example.com with no local part is a wildcard that matches every address in the domain. You can also be surgical — for example, archive only one user’s mail with ceo@example.com    outmails@example.com, or list several lines for several addresses.

Step 4 — Compile the Tables with postmap

Postfix does not read these flat files directly during mail flow. The postmap utility compiles each into an indexed Berkeley DB (.db) file so lookups are fast even with thousands of entries.

  1. sudo postmap /etc/postfix/recipient_bcc — produces /etc/postfix/recipient_bcc.db.
  2. sudo postmap /etc/postfix/sender_bcc — produces /etc/postfix/sender_bcc.db.

Remember: any time you edit one of these source files, you must re-run postmap on it. Postfix uses the compiled .db, not the text file, so forgetting this step is the single most common reason a change “doesn’t work.”

On some modern builds Berkeley DB is deprecated in favor of LMDB. If your Postfix lacks hash: support, use lmdb: instead — check what is available with postconf -m (the list of supported map types).

Step 5 — Wire the Maps into main.cf

Edit /etc/postfix/main.cf and confirm the core identity directives are correct, then add the two BCC directives. The recommended, safe way to add settings is postconf -e, which edits the file in place without you hand-editing it:

  • sudo postconf -e 'myhostname = mail.example.com'
  • sudo postconf -e 'mydomain = example.com'
  • sudo postconf -e 'myorigin = $mydomain'
  • sudo postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain'
  • sudo postconf -e 'inet_interfaces = $myhostname, localhost'
  • sudo postconf -e 'mynetworks = 192.168.124.0/24, 127.0.0.0/8'
  • sudo postconf -e 'recipient_bcc_maps = hash:/etc/postfix/recipient_bcc'
  • sudo postconf -e 'sender_bcc_maps = hash:/etc/postfix/sender_bcc'

If you prefer one combined archive instead of split in/out boxes, skip the two maps and use a single line: sudo postconf -e 'always_bcc = archive@example.com'. Note that always_bcc fires for every message and cannot be filtered, so it can produce a very large mailbox quickly.

Step 6 — Validate and Reload

Before restarting, sanity-check the configuration so you do not take down mail flow with a typo.

  1. Run sudo postfix check — it reports syntax errors, bad permissions, and missing files.
  2. Confirm the directives took effect: postconf recipient_bcc_maps sender_bcc_maps.
  3. Apply the changes without dropping connections: sudo postfix reload (or sudo systemctl reload postfix). A full restart works too: sudo systemctl restart postfix.
  4. Enable Postfix at boot: sudo systemctl enable postfix.

Step 7 — Verify the Archiving Works

Testing is the part too many admins skip. Send real messages and confirm copies land where expected.

  1. Test incoming: from an external account, send a message to any user on the domain (for example joe@example.com). A copy must appear in the inmails mailbox.
  2. Test outgoing: have a domain user send a message to an outside address. A copy must appear in the outmails mailbox.
  3. Read the archive mailboxes: for classic mbox delivery, check /var/mail/inmails and /var/mail/outmails with sudo mail -u inmails or simply sudo tail /var/mail/inmails.
  4. Watch the logs in real time: on RHEL-family systems run sudo tail -f /var/log/maillog; on Debian/Ubuntu use sudo tail -f /var/log/mail.log. With journald-only setups use sudo journalctl -u postfix -f. You should see each message logged once for its real recipient and again for the BCC copy.

Each delivered copy shows a separate queue ID and status=sent line in the log, which is your proof that journaling fired.

Common Pitfalls and How to Avoid Them

SymptomLikely causeFix
No archive copies appearEdited the text map but never re-ran postmapRun postmap on the file and postfix reload
Only outgoing (or only incoming) capturedSwapped the meaning of sender vs recipient mapsRecipient map = inbound, sender map = outbound
Postfix will not start, “Address already in use” on port 25Sendmail or another MTA is bound to 25Stop the conflicting service: sudo systemctl stop sendmail then restart Postfix
Mail loops or duplicated copiesArchive mailbox itself matches a BCC ruleUse a dedicated address not covered by the wildcard, or exclude it
Archive mailbox fills the diskNo rotation or quota on the journalMount a separate volume; add cron-based rotation/compaction

Port-25 conflict in detail. If the log shows fatal: bind 0.0.0.0 port 25: Address already in use, another mail daemon owns the socket. Identify it with sudo fuser -uv 25/tcp or sudo ss -lptn 'sport = :25'. Sendmail being installed and running by default is the classic culprit; stop it, mask it, then restart Postfix.

Privacy and legality. Silently archiving all mail can carry legal and employee-notification obligations depending on your jurisdiction. Document the policy, restrict access to the archive mailboxes, and protect the journals at rest.

Encryption caveat. BCC journaling captures the message as Postfix sees it. If clients use end-to-end encryption (S/MIME or PGP), the archived copy is encrypted too and will not be searchable in plaintext.

Scaling Beyond Flat Files

Flat-file maps are perfect for a single domain or a handful of rules. Larger environments outgrow them:

  • Database-backed maps. Point recipient_bcc_maps at mysql:/etc/postfix/recipient_bcc.cf (or pgsql:) so you can add, change, or remove archiving rules with a single SQL statement — no postmap recompile needed.
  • Dedicated archiving systems. For real compliance retention, deliver the journal mailbox into a purpose-built archiver (for example MailArchiva, Benno MailArchiv, or a commercial WORM-storage product) that provides indexing, retention policies, and immutable storage.
  • Virtual domains. If you host multiple domains with virtual_alias_maps and virtual_alias_domains, the same BCC directives apply — just key your tables on each domain you need to journal.

Whichever backend you choose, the core mechanism is identical: Postfix looks up the sender or recipient, finds an archive address, and quietly delivers an extra copy.

Key Takeaways

  • Use the built-ins: recipient_bcc_maps archives inbound mail and sender_bcc_maps archives outbound mail — no extra software required.
  • Recompile after every edit: always run postmap on a map file and then postfix reload, or your changes are ignored.
  • Separate in/out boxes (inmails, outmails) make auditing far easier than a single always_bcc dump.
  • Verify with the logs: watch /var/log/maillog (or mail.log) and read the archive mailboxes to confirm copies actually arrive.
  • Plan for scale and compliance: move to SQL-backed maps or a dedicated archiver, and address disk growth, retention, and privacy law up front.

Frequently Asked Questions

Will senders or recipients see the BCC archive copy?

No. Postfix adds the archive recipient at the envelope level only, so it never appears in any message header. Neither party can tell the mail was journaled, and they cannot disable it.

What is the difference between always_bcc and the sender/recipient maps?

always_bcc copies every single message to one address with no filtering. The map-based directives let you choose which senders or recipients to archive and route incoming and outgoing mail to different mailboxes, giving you finer control and cleaner archives.

Why are no copies showing up even though my config looks right?

The most common cause is forgetting to run postmap after editing the text map — Postfix reads the compiled .db, not the source file. Re-run postmap, then postfix reload, and re-test. Also confirm the addresses in the map actually match your envelope sender/recipient.

Does this work on modern Postfix and current Linux distros?

Yes. The directives are unchanged across Postfix versions. The only modern adjustments are using dnf instead of yum, systemctl for service control, and (on some builds) lmdb: in place of hash: if Berkeley DB is not compiled in — check with postconf -m.

Found this useful? Subscribe to @explorenystream on YouTube for more hands-on Linux and sysadmin walkthroughs.