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

> Discover how Openship's mail server automates DKIM, SPF, and DMARC authentication. It generates keys, publishes DNS records, and validates them for robust email security.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: internals
- Published: 2026-07-21

---

**Openship automatically provisions a complete email authentication stack by generating 2048-bit DKIM keys, publishing SPF and DMARC DNS records, and validating them through an integrated DNS-scan service.**

Openship (oblien/openship) ships with a production-ready mail server powered by **iRedMail** and **amavisd-new** that eliminates manual DNS configuration for transactional email. The platform handles the entire lifecycle of email authentication—from cryptographic key generation to real-time DNS validation—ensuring outbound messages pass modern anti-spam filters without manual TXT record management.

## Architecture Overview

The mail authentication system operates through three distinct layers defined in the source code: **provisioning**, **DNS record creation**, and **administration/validation**.

### Provisioning Layer

When a domain is added, the system initiates cryptographic provisioning via `packages/core/src/mail-server/routing/build‑routes.ts` (lines 109‑118). This layer executes `amavisd‑new genrsa` to create a 2048-bit RSA key pair, writing the private key to `/var/lib/dkim/<domain>.pem` and updating Amavis configuration at `/etc/amavis/conf.d/50-user` with a `dkim_key` directive.

### DNS Record Management

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 five record types: `a`, `mx`, `spf`, `dkim`, and `dmarc`. These records persist in the `mail_state` database table, creating a single source of truth for DNS configuration that survives container restarts.

### Administration and Validation

Two services handle ongoing verification:

- **[`domain-dns.service.ts`](https://github.com/oblien/openship/blob/main/domain-dns.service.ts)** (lines 221‑242): Injects DKIM values into DNS payloads when domains are registered
- **[`dns-scan.service.ts`](https://github.com/oblien/openship/blob/main/dns-scan.service.ts)** (lines 295‑327): Periodically queries public DNS to verify TXT records match the persisted state, detecting truncation or misconfiguration

## DKIM Key Generation and Configuration

DomainKeys Identified Mail (DKIM) signing requires a private key stored on the server and a corresponding public key published via DNS.

When you execute `openship mail add-domain <domain>` or use the dashboard, the `provisionDomainDkim` function 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 1020‑1034) performs the following:

```bash

# Executed internally by the provisioning service

amavisd genrsa /var/lib/dkim/example.com.pem 2048

```

The service then parses the generated public key and constructs the DKIM TXT record value. The private key remains on the filesystem at `/var/lib/dkim/<domain>.pem`, while Amavis configuration (`50-user`) receives an entry mapping the domain to its key path:

```perl
dkim_key('example.com', 'dkim', '/var/lib/dkim/example.com.pem');

```

This enables `enable_dkim_signing = 1` in Amavis, automatically signing all outbound mail headers with the domain-specific key.

## DNS Record Specifications

Openship generates three mandatory TXT records for complete email authentication, accessible via the `GET /mail/dns/:domain` endpoint defined 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).

### DKIM Records

The DKIM TXT record uses the selector `dkim` and follows standard format:

```

Name:  dkim._domainkey.example.com
Type:  TXT
Value: v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...

```

The implementation in [`build-routes.ts`](https://github.com/oblien/openship/blob/main/build-routes.ts) constructs this value by base64-encoding the RSA public key and wrapping it with the DKIM version and key type identifiers.

### SPF and DMARC Policies

**SPF Record**: Published at the root domain with a restrictive policy allowing only the server's A and MX records:

```

v=spf1 a mx -all

```

**DMARC Record**: Published at `_dmarc.example.com` with a strict rejection policy and aggregate reporting:

```

v=DMARC1; p=reject; rua=mailto:postmaster@example.com

```

Both records are generated as static strings in [`mail.service.ts`](https://github.com/oblien/openship/blob/main/mail.service.ts) (lines 960‑962) during the domain provisioning workflow, though administrators can override these defaults through the database if specific configurations are required.

## Validation and API Integration

Openship continuously monitors DNS propagation to prevent email delivery failures caused by missing or malformed records.

### DNS Verification Service

The [`dns-scan.service.ts`](https://github.com/oblien/openship/blob/main/dns-scan.service.ts) module implements a background worker that compares the `PersistedDnsRecord` values stored in the database against live DNS queries. If the DKIM public key retrieved from `dig TXT dkim._domainkey.<domain>` does not match the stored fingerprint, the service updates the `error` field in the mail state record and surfaces a warning in the dashboard.

### API and CLI Interfaces

Administrators can retrieve current DNS requirements through multiple interfaces:

**REST API** ([`apps/dashboard/src/lib/api/mail.ts`](https://github.com/oblien/openship/blob/main/apps/dashboard/src/lib/api/mail.ts), line 153):

```bash
curl https://api.openship.example.com/mail/dns/example.com

```

Returns a JSON payload containing all five record types (`a`, `mx`, `spf`, `dkim`, `dmarc`) with their exact values.

**Command Line**:

```bash
openship mail dns example.com

```

This command outputs a markdown-formatted table suitable for copy-paste into DNS provider consoles, implemented in [`apps/cli/src/commands/mail.ts`](https://github.com/oblien/openship/blob/main/apps/cli/src/commands/mail.ts) (line 87).

## Summary

- **Automatic DKIM provisioning**: Generates 2048-bit RSA keys via `amavisd-new`, stores them in `/var/lib/dkim/`, and configures Amavis (`50-user`) for signing.
- **Complete DNS coverage**: Creates DKIM (`dkim._domainkey`), SPF (root domain), and DMARC (`_dmarc`) TXT records with secure defaults (`p=reject`).
- **Persistent state management**: Stores record values in the `mail_state` table using the `PersistedDnsRecord` schema for durability.
- **Continuous validation**: The DNS-scan service verifies public records against stored values, detecting truncation or propagation delays.
- **Multi-interface access**: Retrieve records via REST API (`GET /mail/dns/:domain`) or CLI (`openship mail dns`).

## Frequently Asked Questions

### How does Openship generate DKIM keys?

Openship calls `amavisd genrsa /var/lib/dkim/<domain>.pem` to create a 2048-bit RSA key pair, then registers the private key path in `/etc/amavis/conf.d/50-user` while extracting the public component for DNS publication. This process is triggered automatically when adding a domain through the CLI or dashboard.

### What DNS records does Openship create for email authentication?

The system produces three TXT records: a **DKIM** record at `dkim._domainkey.<domain>` containing the RSA public key, an **SPF** record at the root domain with `v=spf1 a mx -all`, and a **DMARC** record at `_dmarc.<domain>` with `v=DMARC1; p=reject; rua=mailto:postmaster@<domain>`.

### How can I verify my DNS records are correctly configured?

Use the built-in DNS scan service defined in [`apps/api/src/modules/mail/admin/dns-scan.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/mail/admin/dns-scan.service.ts), which periodically checks public DNS against the persisted `mail_state` records. Alternatively, query the `GET /mail/dns/:domain` endpoint or run `openship mail dns <domain>` to view the expected values and compare them against your DNS provider's configuration.

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

Private keys are stored as PEM files at `/var/lib/dkim/<domain>.pem` with restrictive filesystem permissions, while the Amavis configuration at `/etc/amavis/conf.d/50-user` contains the `dkim_key` directives mapping domains to their respective key paths. Public keys are not stored on disk but are generated on-demand for DNS responses.