# Mail System

Source: https://www.corepanel.net/docs/mail-system/
Last updated: 2026-09-09
Part of the CorePanel documentation — https://www.corepanel.net/docs

---

CorePanel includes a complete email stack designed for reliability, security, and high deliverability. This document provides technical details about the mail system architecture and configuration.

## Mail Stack Components

CorePanel's mail system is built on industry-standard components:

| Component | Role |
|-----------|------|
| **Postfix** | SMTP server (sending/receiving mail) |
| **Dovecot** | IMAP/POP3 server and LMTP delivery |
| **Rspamd** | Spam filtering and DKIM signing |
| **corepanel-auth** | Authentication broker |
| **Roundcube** | [Webmail](https://www.corepanel.net/docs/email/webmail), served at `webmail.<domain>` |

### Architecture Overview

```
                    ┌─────────────────┐
    Incoming Mail   │                 │
    ───────────────►│     Postfix     │
                    │   (SMTP MTA)    │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │     Rspamd      │
                    │  (filtering)    │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  Dovecot LMTP   │◄──── User Access (IMAP/POP3)
                    │   (delivery)    │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ corepanel-auth  │◄──── Authentication
                    │   (broker)      │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │ corepanel-core  │◄──── User/Mailbox Database
                    │   (SQLite)      │
                    └─────────────────┘
```

## Email Account Management

### Account Creation

When a mailbox is created through the CorePanel interface or API, the following process occurs:

1. **corepanel-api** receives the creation request
2. **corepanel-core** validates the domain ownership and quota limits
3. **corepanel-core** stores the mailbox metadata in SQLite
4. **corepanel-core** syncs the mailbox to Postfix lookup database
5. Dovecot creates the maildir structure on first delivery

```bash
# Mailbox directory structure
/home/{account}/mail/{domain}/{localpart}/
├── cur/      # Read messages
├── new/      # Unread messages
└── tmp/      # Temporary storage during delivery
```

### Mailbox Data Model

Each mailbox stores the following information:

| Field | Description |
|-------|-------------|
| `Localpart` | Email local part (e.g., "user") |
| `Username` | Full email address (e.g., "user@example.com") |
| `Password` | bcrypt hash |
| `QuotaMB` | Storage quota in megabytes |
| `LinuxUID/GID` | OS-level user/group for mail delivery |
| `HomeDir` | Account home directory |
| `Active` | Enable/disable flag |

### Quota Management

Each mailbox has configurable storage quotas stored in the CorePanel database. Dovecot queries these quotas through the `corepanel-auth` service in real-time, ensuring:

- Accurate quota enforcement during delivery
- Instant quota updates without service restarts
- Per-mailbox and per-domain quota limits

## The corepanel-auth Service

The `corepanel-auth` service is a critical component that acts as an **authentication broker** between mail services and the CorePanel database.

### Why corepanel-auth?

Traditional mail setups require direct database queries from Postfix and Dovecot. This approach has several drawbacks:

- Database credentials exposed in multiple config files
- No centralized authentication logic
- Difficult to implement custom authentication rules
- Complex query configuration in mail services

`corepanel-auth` solves these problems by providing a unified authentication endpoint.

### How It Works

```
┌──────────────┐     checkpassword protocol   ┌─────────────────┐
│   Dovecot    │ ───────────────────────────►│                 │
└──────────────┘                             │  corepanel-auth │
                                             │                 │
┌──────────────┐     socketmap lookups       │   (JSON-RPC)    │
│   Postfix    │ ───────────────────────────►│                 │
└──────────────┘                             └────────┬────────┘
                                                      │
                                             ┌────────▼────────┐
                                             │  corepanel-core │
                                             │    (database)   │
                                             └─────────────────┘
```

### Authentication Flow

1. User attempts IMAP/SMTP login
2. Dovecot calls the checkpassword helper binary
3. Helper connects to `corepanel-auth` via Unix socket
4. `corepanel-auth` queries `corepanel-core` via JSON-RPC (`core.AuthenticateMailUser`)
5. Response returns UID, GID, home directory, maildir path, and quota
6. Helper translates response into Dovecot environment variables

### Postfix Integration

Postfix uses socketmap lookups to query `corepanel-auth` for:

| Lookup | Purpose |
|--------|---------|
| `domains` | Check if domain is configured for mail |
| `alias` | Get destination for email aliases |

### Socket Locations

```
/var/run/corepanel/corepanel-mail-auth.sock
```

### Authentication Audit Trail

`corepanel-auth` is the only process on the server that sees **every** credential
check — IMAP, POP3, SMTP submission, FTP and FTPS — with the client's address
attached. Every one of those checks is recorded in a dedicated SQLite database:

```
/var/lib/corepanel-auth/events.sqlite
```

Each row holds the time, the service, the action, the username, the client and
server addresses, the outcome (`ok`, `fail` or `error`) and a reason such as
`wrong_password` or `unknown_user`. Mutations of the mail map arriving from
`corepanel-core` — a mailbox added, an alias deleted — are recorded too, so a
mailbox that suddenly stops receiving mail is a lookup rather than a mystery.

**No password material is ever written.** Not the value, not a hash, not a
length. That is a rule of the design, not a default that could be changed.

A **country** column is filled in from the GeoLite2 database that `corehttpd`
downloads and keeps fresh at `/var/lib/corehttpd/geoip/`. `corepanel-auth` only
reads that file — it never downloads anything itself, which is why the broker
that sits in front of every credential on the box makes no outbound connections
at all. If the database is missing the country is simply left empty and
everything else keeps working.

Two limits keep the store bounded, and both are reported rather than applied
silently:

| Limit | Default | What happens |
|-------|---------|--------------|
| Retention | 15 days | Older events are deleted every 5 minutes |
| Row cap | 500 000 | The oldest events are dropped first, so a sustained attack cannot fill the disk |

Auditing never delays or blocks a login. Writes are batched onto a background
goroutine, and if that queue is ever full the event is **dropped and counted** —
losing visibility is an acceptable failure, delaying every mail login on the
server is not.

#### Reading it

In the panel it is **Security → Login activity**, with a per-mailbox *Recent access*
block in each mailbox's Security section and a failed-logins card on the dashboard —
see [Access Protection](https://www.corepanel.net/docs/security/access-protection).

The same store is queryable from the command line on every edition:

```bash
# Everything that failed in the last hour
corepanel auth events --result fail --since 1h

# The addresses with the most failed logins this week
corepanel auth attackers --since 168h

# When and from where did this mailbox last log in?
corepanel auth user ana@example.com
```

`corepanel auth attackers` is the one to reach for during an attack. Its
**ACCOUNTS** column separates the two cases that look identical in a log file:
one account failing repeatedly from a single address is almost always a stale
password on somebody's phone, while one address failing against many accounts is
a dictionary run. `corepanel auth stats` adds the health of the audit trail
itself — how many events are stored, how far back they go, and how many were
dropped because the writer fell behind.

The full command reference is in [CLI Reference](https://www.corepanel.net/docs/cli#corepanel-auth).

> **Replaces the old audit files**
>
> Earlier versions wrote `audit-mail.log` (and, in `corepanel-core`,
> `audit-ftp.log`): rolling JSON files capped at a few megabytes that nothing ever
> read. Both are gone. A leftover copy under `/var/lib/corepanel-auth/logs/` or
> `/var/lib/corepanel/logs/` after an upgrade is inert and can be deleted.
> **Service Dependencies**
>
> Always ensure `corepanel-auth` and `corepanel-core` are running before starting Dovecot or Postfix. The services depend on authentication being available.
## SPF (Sender Policy Framework)

SPF is a DNS-based email authentication method that specifies which mail servers are authorized to send email on behalf of your domain.

### How SPF Works

1. Sending server connects to receiving server
2. Receiving server checks the sender's domain SPF record
3. SPF record lists authorized IP addresses/hostnames
4. If sender's IP matches, email passes SPF check

### SPF Record Format

A typical SPF record looks like:

```ini
v=spf1 a mx ip4:SERVER_IP ~all
```

| Mechanism | Description |
|-----------|-------------|
| `v=spf1` | SPF version identifier |
| `a` | Allow the domain's A record IP |
| `mx` | Allow the domain's MX server IPs |
| `ip4:` | Allow specific IPv4 addresses |
| `~all` | Soft fail for unauthorized senders |

### SPF in CorePanel

CorePanel **automatically creates** SPF records when a domain is added. The default record is:

```ini
example.com. IN TXT "v=spf1 a mx ~all"
```

This default configuration:
- Allows the domain's A record IP to send mail
- Allows the domain's MX servers to send mail
- Uses soft fail (`~all`) for other sources

The system verifies SPF validity by:
1. Performing DNS TXT lookup on the domain
2. Searching for records starting with `v=spf1`
3. Displaying the status in the domain's mail configuration

### SPF Best Practices

- **Use `~all` (soft fail)** during initial setup, switch to `-all` (hard fail) once verified
- **Keep records under 10 DNS lookups** to avoid SPF permerror
- **Include third-party senders** (e.g., `include:_spf.google.com` for Google Workspace)

> **SPF Limitations**
>
> SPF only validates the envelope sender (MAIL FROM), not the header From address. Use DKIM and DMARC for complete email authentication.
## DKIM (DomainKeys Identified Mail)

DKIM adds a cryptographic signature to outgoing emails, allowing receiving servers to verify that the message was sent by an authorized server and wasn't modified in transit.

### How DKIM Works

1. **Key Generation**: CorePanel generates a 2048-bit RSA key pair per domain
2. **DNS Publication**: Public key is published as a TXT record
3. **Message Signing**: Rspamd signs outgoing emails with the private key
4. **Verification**: Receiving servers verify signatures using the public key

### DKIM Key Management

CorePanel uses **Rspamd** for DKIM key generation and signing:

| Setting | Value |
|---------|-------|
| Selector | `cp1` |
| Key size | 2048-bit RSA |
| Key location | `/var/lib/rspamd/dkim/` |

### DKIM Signing Process

```
┌─────────────────┐
│  Outgoing Email │
└────────┬────────┘
         │
         ▼
┌─────────────────┐     ┌──────────────────┐
│    Postfix      │────►│     Rspamd       │
│                 │     │    (milter)      │
└─────────────────┘     └────────┬─────────┘
                                 │
                        ┌────────▼─────────┐
                        │  Private Key     │
                        │  (per domain)    │
                        └────────┬─────────┘
                                 │
                        ┌────────▼─────────┐
                        │ DKIM-Signature   │
                        │ header added     │
                        └──────────────────┘
```

### DKIM DNS Record

CorePanel generates DKIM public keys with the selector `cp1`:

```ini
cp1._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh..."
```

| Tag | Description |
|-----|-------------|
| `v=DKIM1` | DKIM version |
| `k=rsa` | Key type (RSA) |
| `p=` | Base64-encoded public key |

### Setting Up DKIM

CorePanel handles DKIM setup automatically:

1. **Key Generation**: When a domain is added, CorePanel generates the DKIM key pair
2. **DNS Publication**: The public key is automatically published to the DNS zone at `cp1._domainkey.yourdomain.com`
3. **Signing Activation**: Rspamd begins signing outgoing mail immediately

No manual DNS configuration is required since CorePanel manages the DNS zones.

### Verifying DKIM Setup

Send a test email to a Gmail address and check the headers:

```
Authentication-Results: mx.google.com;
       dkim=pass header.d=example.com header.s=cp1;
       spf=pass smtp.mailfrom=user@example.com
```

### DKIM Key Rotation

CorePanel supports DKIM key rotation with incremental selectors to maintain email deliverability during the transition period.

#### Why Rotate DKIM Keys?

- **Security**: Periodic key rotation limits the impact of potential key compromise
- **Compliance**: Some security policies require regular cryptographic key rotation
- **Key Size Updates**: Upgrade from older 1024-bit keys to 2048-bit keys

#### How Key Rotation Works

When you rotate a DKIM key through the CorePanel interface:

1. **New Selector Generated**: A new selector is created incrementally (cp1 → cp2 → cp3...)
2. **Key Pair Created**: A new 2048-bit RSA key pair is generated
3. **Selector Maps Updated**: Rspamd is configured to use the new selector for signing
4. **DNS Record Added**: If DNS is managed by CorePanel, the new DKIM record is automatically added
5. **Grace Period**: The old key remains valid for 7 days

#### Selector Mapping

CorePanel uses rspamd's `selector_map` and `path_map` configuration to support different selectors per domain:

```
/var/lib/rspamd/dkim/selectors.map    # Maps domains to selectors
/var/lib/rspamd/dkim/paths.map        # Maps domains to key file paths
```

Example content:
```
# selectors.map
example.com cp2
other.com cp1

# paths.map
example.com /var/lib/rspamd/dkim/example.com.cp2.key
other.com /var/lib/rspamd/dkim/other.com.cp1.key
```

#### Key File Naming

DKIM keys are stored with the selector in the filename:

```
/var/lib/rspamd/dkim/
├── example.com.cp1.key    # Old key (expired)
├── example.com.cp1.txt    # Old public key record
├── example.com.cp2.key    # Current active key
└── example.com.cp2.txt    # Current public key record
```

#### Grace Period Cleanup

A daily job (4:00 AM server time) automatically cleans up expired selectors:

1. Removes key files from `/var/lib/rspamd/dkim/`
2. Deletes the DNS record (if DNS is managed)
3. Removes the selector from the database

#### Rotating keys

DKIM key rotation is triggered from the **panel** (the domain's mail
configuration). There is no `corepanel` CLI subcommand for DKIM. Programmatically,
rotation is the `core.RotateDKIMKey` JSON-RPC method on corepanel-core, and the
active selectors for a domain are available through `sys.ListDKIMSelectors` on
corepanel-sys — the same methods the panel uses. From a shell you can still
inspect the result directly with the selector maps and `dig` (see below).

#### Troubleshooting Key Rotation

**Check active selector:**
```bash
cat /var/lib/rspamd/dkim/selectors.map | grep example.com
```

**Verify new DKIM record:**
```bash
dig +short TXT cp2._domainkey.example.com
```

**Check rspamd configuration:**
```bash
cat /etc/rspamd/local.d/dkim_signing.conf
```

**View rspamd logs for signing issues:**
```bash
journalctl -u rspamd | grep -i dkim
```

> **Grace Period**
>
> During the 7-day grace period after rotation, both the old and new selectors have valid DNS records. This ensures emails signed with the old key before rotation can still be verified by receiving servers.
## Mail Mode Detection

CorePanel automatically detects the mail capabilities for each domain:

| Mode | Description | Requirements |
|------|-------------|--------------|
| **None** | No mail capability | - |
| **Inbound** | Can receive mail | MX points to server |
| **Outbound** | Can send mail | DKIM valid or DNS managed |
| **Full** | Send and receive | MX + DKIM configured |

The system performs the following DNS checks:
- **MX Records**: Verifies MX points to server hostname or IP
- **DKIM Record**: Validates `cp1._domainkey` TXT record matches expected key
- **SPF Record**: Detects presence of `v=spf1` record

## Server Identity (HELO)

Postfix announces a name in every SMTP conversation it opens — the `HELO`/`EHLO`
greeting, also stamped into `Received` headers. Receiving mail servers weigh it
heavily: a greeting that is not a fully qualified name, or that resolves nowhere,
is one of the cheapest reasons to reject a message or score it as spam.

CorePanel sets it to **the panel domain**, and keeps it there:

- At install time it is seeded from the machine's own hostname.
- **When you set or change the panel domain, `myhostname` is updated to match** and
  Postfix is reloaded. The panel domain is used because it is, by construction, a
  name that resolves to this server.

This matters most on a server provisioned from a cloud or marketplace image, where
the machine boots as something like `almalinux-2gb-nyc1-01` — not a fully qualified
name, resolving nowhere. Until you set the panel domain, that is what the server
announces.

Check the current value with:

```bash
postconf -h myhostname
```

> **The reverse DNS record is yours to set**
>
> A correct HELO is necessary but not sufficient. Receiving servers also compare it
> against the **PTR (reverse DNS) record of your IP**, and only your hosting provider
> can set that — CorePanel cannot. Set the PTR for the server's IP to the same panel
> domain in your provider's control panel.
## TLS Certificate Management

### The default (host) certificate

Both Postfix and Dovecot fall back to the same certificate when a connection
arrives with no SNI name, or with a name no domain on this server holds: a fixed
host-certificate path (`/var/lib/corehttpd/hostcert/`). CoreHttpd obtains the certificate, because
issuance belongs to whatever terminates TLS on port 443; `corepanel-sys` owns that
path and keeps it up to date, because the mail services are its to reload. Its
lifecycle:

1. **Self-signed**, written as soon as the panel has a domain, so TLS works
   immediately.
2. **Real certificate**, promoted automatically onto the same path once the panel
   domain resolves to the server and the certificate is issued. Postfix and Dovecot
   are reloaded on the change.

The promotion happens within seconds of issuance: `corepanel-sys` watches the
directory CoreHttpd files certificates in, so the certificate obtained during your
first HTTPS visit to the panel is the one mail is already serving by the time you
open a mail client. A periodic check every five minutes covers anything the watch
misses, so the worst case is a few minutes rather than a stall.

While the panel has **no domain at all**, no host certificate is issued and mail
falls back to the system's self-signed certificate. Mail still flows — TLS is
opportunistic — but clients will warn about the certificate until you set the panel
domain.

The host certificate is also filled in from the certificates the server already
holds. If nobody has ever reached the panel over HTTPS under its own hostname —
an admin who works from the server's IP, or from an old URL after a migration —
CoreHttpd never had a handshake to issue on, and the path would keep the
self-signed bootstrap indefinitely. When one of the domain certificates in
`/var/lib/cp-sys/ssl/certs/` covers the hostname (a `*.example.com` wildcard
covers `server.example.com`), `corepanel-sys` promotes that one instead. A
certificate obtained for the hostname itself always wins over one that merely
covers it.

### Per-domain certificates over SNI

Both daemons present the right certificate per domain, selected by the SNI name
the client sends, so a mail client configured for `mail.example.com` is handed
`example.com`'s certificate and not the server's own.

- Certificates are scanned from `/var/lib/cp-sys/ssl/certs/`.
- **The names come from the certificate, not from the directory it sits in.** A
  wildcard `*.example.com` is expanded into the names clients actually ask for —
  `mail.`, `webmail.`, `imap.`, `smtp.`, `pop.`, `pop3.` — because neither daemon
  matches wildcards in an SNI table. A name stated literally in a certificate
  wins over the same name derived from a wildcard.
- **Dovecot** reads a `local_name` block per name from
  `/etc/dovecot/conf.d/99-corepanel-ssl.conf`.
- **Postfix** reads `tls_server_sni_maps`, a table at
  `/etc/postfix/corepanel_sni.map` compiled with `postmap -F`. Postfix does not
  read the PEM files at handshake time — the compiled `.db` embeds the
  certificates and their keys, which is why it is owned by Postfix's `mail_owner`
  and mode `0640`, and why it is rebuilt on every renewal and not only when a
  domain is added.
- Both files are generated from the same scan, so the two daemons always publish
  the same set of names. Both are regenerated whenever a certificate is issued,
  renewed or removed, and the services reloaded.

> **Postfix 3.4 or newer**
>
> `tls_server_sni_maps` was introduced in Postfix 3.4 (RHEL 8 ships 3.5). On an
> older Postfix, `corepanel-sys` logs a warning and leaves the configuration
> alone: every connection is then served the default host certificate, and mail
> clients using `mail.<domain>` will report a name mismatch.
## Troubleshooting

### Common Issues

**Mail not sending/receiving:**
```bash
# Check service status
systemctl status postfix dovecot rspamd corepanel-auth

# Check mail logs
tail -f /var/log/maillog
```

**Authentication failures:**
```bash
# Check auth socket exists and has correct permissions
ls -la /var/run/corepanel/corepanel-mail-auth.sock

# Check corepanel-auth logs
journalctl -u corepanel-auth -f
```

**SPF/DKIM failures:**
```bash
# Test SPF record
dig +short TXT example.com | grep spf

# Test DKIM record
dig +short TXT cp1._domainkey.example.com
```

### Mail Queue Management

```bash
# View mail queue
postqueue -p

# Flush the queue
postqueue -f

# Delete all queued mail (use with caution)
postsuper -d ALL
```

## Additional Resources

- [Postfix Documentation](http://www.postfix.org/documentation.html)
- [Dovecot Wiki](https://wiki.dovecot.org/)
- [Rspamd Documentation](https://rspamd.com/doc/)
- [SPF Record Syntax](https://www.openspf.org/SPF_Record_Syntax)
