# How Openship's Built-in Mail Server Handles DKIM, SPF, and DMARC

> Openship auto-configures DKIM SPF and DMARC for authenticated email. Learn how our built-in mail server simplifies email security and delivery.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: how-to-guide
- Published: 2026-07-23

---

**Openship automatically provisions DKIM keys, generates SPF and DMARC DNS records, and validates their publication to ensure authenticated email delivery.**

The `oblien/openship` repository ships a production-ready mail server built on **iRedMail** and **amavisd-new** that eliminates manual DNS configuration. When you add a domain, Openship handles the complete email authentication stack—generating 2048-bit RSA keys for DKIM, creating restrictive SPF policies, and setting up DMARC reporting—without requiring you to manually edit zone files.

## Three-Layer Authentication Architecture

Openship splits email authentication into three logical layers, each handled by distinct modules in the codebase.

### 1. Key Provisioning and Configuration

In [`packages/core/src/mail-server/routing/build-routes.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/mail-server/routing/build-routes.ts) (lines 109-118), the system executes `amavisd genrsa` to create a 2048-bit RSA key pair. The private key is written to `/var/lib/dkim/<domain>.pem`, while the public key is parsed and prepared for DNS publication. The configuration is then injected into Amavis via the `50-user` configuration file using the `dkim_key` directive.

### 2. DNS Record Generation

The [`apps/api/src/modules/mail/mail-state.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/mail/mail-state.ts) file (lines 203-267) defines the `PersistedDnsRecord` interface that stores three critical TXT records:

- **DKIM**: `dkim._domainkey.<domain>` containing the public key
- **SPF**: `<domain>` with `v=spf1 a mx -all`
- **DMARC**: `_dmarc.<domain>` with `v=DMARC1; p=reject; rua=mailto:postmaster@<domain>`

These records are persisted in the `mail_state` database table and exposed through the application's API.

### 3. Administration and Validation

The administration layer validates that published DNS records match the generated values. In [`apps/api/src/modules/mail/admin/domain-dns.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/mail/admin/domain-dns.service.ts) (lines 221-242), the `provisionDomainDkim` function orchestrates key generation. The [`dns-scan.service.ts`](https://github.com/oblien/openship/blob/main/dns-scan.service.ts) (lines 295-327) periodically verifies that DKIM, SPF, and DMARC records are reachable and correctly formatted, warning users of truncated keys or missing entries.

## The Domain Onboarding Flow

When you add a domain via the dashboard or CLI, Openship executes a four-step authentication pipeline:

1. **Domain Registration**: Triggered by `openship mail add-domain <domain>` or the dashboard UI, calling the domain-DNS service.
2. **Key Generation**: [`mail.service.ts`](https://github.com/oblien/openship/blob/main/mail.service.ts) (lines 1020-1034) executes `amavisd-new genrsa /var/lib/dkim/<domain>.pem` and updates the Amavis configuration with `dkim_key('<domain>', 'dkim', '<keyPath>')`.
3. **State Persistence**: The system creates `PersistedDnsRecord` entries for A, MX, SPF, DKIM, and DMARC records in the `mail_state` table ([`mail-state.ts`](https://github.com/oblien/openship/blob/main/mail-state.ts), lines 960-1000).
4. **DNS Validation**: The DNS-scan service verifies that `dkim._domainkey.<domain>` resolves correctly and that SPF and DMARC policies are publicly accessible before marking the domain as active.

## Implementation Details

Here is how specific authentication mechanisms are implemented in the source code:

**DKIM Signing**

Outbound mail is DKIM-signed by Amavis with `enable_dkim_signing = 1`. The private key resides at `/var/lib/dkim/<domain>.pem`, and the public key is exposed via the TXT record `dkim._domainkey.<domain>`.

**SPF Policy**

The default SPF record (`v=spf1 a mx -all`) restricts sending to only the server's IP and MX records. This is generated in [`mail.service.ts`](https://github.com/oblien/openship/blob/main/mail.service.ts) (lines 960-962) and can be overridden if your infrastructure requires additional authorized senders.

**DMARC Enforcement**

Openship defaults to a strict `p=reject` policy, ensuring that receivers quarantine or reject mail failing authentication checks. The reporting URI (`rua=mailto:postmaster@${domain}`) aggregates forensic reports for monitoring.

## Retrieving DNS Records via API and CLI

Once generated, you can access the records through multiple interfaces:

**REST API**

Query `GET /mail/dns/:domain` to receive a JSON payload containing `a`, `mx`, `spf`, `dkim`, and `dmarc` records. This endpoint is implemented in [`apps/api/src/modules/mail/mail.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/mail/mail.service.ts) (lines 960-1000) and consumed by the dashboard at [`apps/dashboard/src/lib/api/mail.ts`](https://github.com/oblien/openship/blob/main/apps/dashboard/src/lib/api/mail.ts) (line 153).

**Command Line Interface**

Use the CLI helper for quick copy-paste into your DNS provider:

```bash
openship mail dns

```

This command, implemented in [`apps/cli/src/commands/mail.ts`](https://github.com/oblien/openship/blob/main/apps/cli/src/commands/mail.ts) (line 87), prints a markdown table with the exact TXT values required for your domain registrar.

## Summary

- Openship automates **DKIM key generation** using `amavisd-new` and stores private keys in `/var/lib/dkim/`.
- The system creates **three DNS records** (DKIM, SPF, DMARC) and persists them in the `mail_state` table via `PersistedDnsRecord`.
- **Validation services** continuously scan public DNS to ensure records match the generated values before allowing mail flow.
- Records are accessible via **REST API** (`GET /mail/dns/:domain`) and **CLI** (`openship mail dns`).

## Frequently Asked Questions

### Where are the DKIM private keys stored on the server?

The private keys are stored in `/var/lib/dkim/<domain>.pem` with restricted permissions. The path is registered in Amavis's `50-user` configuration file using the `dkim_key` directive, enabling the mail transfer agent to sign outgoing messages automatically.

### Can I customize the DMARC policy from the default reject setting?

Yes. While Openship defaults to `v=DMARC1; p=reject; rua=mailto:postmaster@<domain>` for maximum security, you can modify the DMARC record value in the `mail_state` table or override it directly in your DNS provider after the initial provisioning.

### How does Openship validate that my DNS records are correct?

The [`dns-scan.service.ts`](https://github.com/oblien/openship/blob/main/dns-scan.service.ts) module performs periodic lookups against public DNS resolvers to verify that TXT records for DKIM, SPF, and DMARC match the expected values stored in the database. If the DKIM key is truncated or a record is missing, the dashboard displays a validation warning.

### What happens if I add a domain using the CLI instead of the dashboard?

Both interfaces trigger the same `provisionDomainDkim` workflow in [`domain-dns.service.ts`](https://github.com/oblien/openship/blob/main/domain-dns.service.ts). The CLI command `openship mail add-domain <domain>` invokes the API, which generates the keys, creates the DNS records, and persists them to `mail_state` identically to the dashboard workflow.