Security

EmailEngine holds your users' mailbox credentials, so you are owed specifics rather than assurances. This page covers what is stored and where, what is encrypted and with what, everything that ever leaves your network, and how to report a problem. The source is available, so every claim here is one your security team can check.

The architecture is the control

EmailEngine runs on your infrastructure. Message content is fetched on demand from the original mailbox and never copied to anyone else's servers, ours included. The strongest security property is the one that needs no policy: your users' mail has nowhere else to go.

The smallest useful footprint

What EmailEngine keeps is a sync index: message IDs, flags and folder structure, in your own Redis. There is no message archive, no attachment store and no webhook delivery log to protect, because none of them exist.

No vendor path in

There is no remote management channel and no backdoor. The only inbound signal EmailEngine accepts from us is the licence validation response, which can invalidate a licence and nothing else: it cannot push code, change configuration or read data. We cannot reach your instance even if you ask us to.

What lives in your Redis, and what never does

Data Where it lives Protection
Mailbox passwords, OAuth2 access and refresh tokens Your Redis Encrypted with AES-256-GCM
OAuth2 client secrets, service keys, TOTP seeds Your Redis Encrypted with AES-256-GCM
API access tokens Your Redis Only the SHA-256 hash; the token itself is shown once and never stored
Message index: IDs, flags, labels, folder structure Your Redis Metadata only, no content
Queued sends and webhook deliveries Your Redis Transient; discarded once completed or permanently failed
Message bodies, attachments, full headers Nowhere Fetched on demand from the original mailbox, never stored
Sent-mail archive, webhook history Nowhere Not kept; EmailEngine is a gateway, not a datastore

Mailbox exports, when you request them, are written as chunked AES-256-GCM streams with owner-only file permissions. The full inventory, including the optional features that change it, is in the compliance documentation.

Encryption

Field-level encryption for everything that could log into a mailbox, with a key that never leaves your side.

AES-256-GCM at rest

Every stored credential, OAuth2 token and secret is encrypted with AES-256-GCM: a fresh random IV and salt per value, an authentication tag checked on every read, and a 256-bit key derived with scrypt.

Your key, your key store

The encryption key comes from the EENGINE_SECRET environment variable, or from a file path for Docker, Kubernetes and Vault-style secret managers. It is never written to Redis and never sent anywhere.

Rotation is built in

A CLI command re-encrypts all stored data under a new key, and accepts multiple previous keys in one pass, so rotating generations of secrets is an operation rather than a project.

Encrypted exports

Mailbox exports are gzip-compressed, then encrypted as chunked AES-256-GCM streams with a per-chunk IV and authentication tag, written with owner-only permissions.

TLS to every mail server

Certificates are validated by default; accepting invalid ones is a deliberate, per-deployment opt-in that the schema itself labels as not for production. The TLS version and cipher floors default to maximum mailbox compatibility and can be raised by environment variable.

Sealed admin sessions

Admin sessions use an authenticated, encrypted cookie with SameSite set, and every session is invalidated the moment the password changes.

Authentication and access control

Tokens that cannot leak from storage

An API token is 32 random bytes, shown once at creation. EmailEngine stores only its SHA-256 hash, so no dump of Redis can recover a working token.

Least-privilege tokens

Tokens can be limited to one account, one scope, an IP or CIDR allowlist, specific HTTP referrers and a per-token rate limit, so the credential in your least-trusted integration carries the smallest possible blast radius.

Modern password hashing

Admin passwords are hashed with PBKDF2-SHA256 at 600,000 iterations, in line with current OWASP guidance, with per-IP and per-username rate limits on the login form.

Two-factor and passkeys

TOTP two-factor authentication with a replay guard that burns each code after use, and WebAuthn passkeys with single-use, short-lived challenges. A password change revokes every passkey.

Single sign-on

OpenID Connect against any provider using the authorization code flow with PKCE, plus an SSO-only mode that disables local logins entirely. Okta is supported as a dedicated integration.

Authorisation that revokes instantly

SSO access can be allow-listed by email, domain or IdP group, and the lists are re-evaluated on every request rather than at login, so removing someone takes effect immediately instead of when their cookie expires.

The dashboard can additionally be restricted to an admin IP allowlist, and unverified IdP email addresses are refused even when they match an allow-list. Details in the security documentation.

An audit trail your SIEM can drink from

Everything is structured JSON on standard output, ready for whatever log pipeline you already run.

Every authentication event

Successful and failed logins, TOTP attempts and replays, passkey registrations, deletions and each distinct failure reason, logouts and rate-limit hits, all with the acting user and remote address.

Token lifecycle

Creating and deleting access tokens is logged with the token's hash and its restriction metadata, so you can reconstruct who could access what, and when that changed.

Redaction by default

Authorization headers, cookies and raw network packets are redacted from logs, and settings values that decrypt are deliberately kept out of error messages.

Protocol-level evidence

Per-account IMAP protocol logging can be switched on while you investigate an incident, then downloaded as a transcript of exactly what the mail server said.

Everything that ever leaves your network

This is the complete list, from the source code, of what EmailEngine sends anywhere other than your own mail servers and webhook endpoints. It is short enough to read, which is the point.

Licence validation

At most once per 24 hours, subscription licences check in with the licence key, the EmailEngine version, the application name and an instance identifier. If the check cannot be made, a 28-day grace period keeps your instance running. Perpetual licences never make this call at all: they verify offline, so neither the validation request nor the digest below exists for them.

An anonymised feature digest

Alongside validation, an optional digest reports which features are enabled and coarse order-of-magnitude buckets rather than raw counts. It never contains addresses, URLs, credentials or message data, and EENGINE_BEACON_DISABLED turns it off.

Update check

A periodic request to the GitHub releases API drives the "update available" notice in the dashboard. It sends a user-agent string and nothing else.

Only when you click

Deliverability tests, IMAP settings autodiscovery and Let's Encrypt certificate issuance each contact their respective services, and each happens only on an explicit action or opt-in, never in the background.

Error reporting is opt-in

Sentry error reporting is off until you enable it, and you can point it at your own self-hosted Sentry instead of ours.

And nothing else

No telemetry beyond the digest above, no analytics, no content ever. The source is available, so this claim is verifiable rather than contractual: grep it.

Webhooks built for zero-trust receivers

Signed payloads

Every delivery carries an HMAC-SHA256 signature over the exact request body, so your endpoint can verify that the event came from your instance and was not altered in flight.

Deduplication built in

A stable event ID and attempt counter accompany every delivery, so retried webhooks are recognised rather than double-processed.

SSRF-filtered targets

Webhook destinations are checked against an egress policy that blocks cloud metadata endpoints by default and can block all private address space. Redirects are not followed, so an allowed host cannot bounce a request to a blocked one.

Supply chain

An SBOM your instance serves

Every install ships its SPDX software bill of materials and serves it at /sbom.json, so your instance can hand your auditor its own dependency inventory.

npm provenance

The npm package is published with a provenance attestation, giving a publicly verifiable link from the package on the registry back to the exact source and build that produced it.

A hardened container image

The Docker image builds from a digest-pinned Node base, runs as a non-root user with a proper init process, and installs exact-pinned production dependencies from the lockfile.

Pinned, curated dependencies

Every dependency version is pinned exactly, and updates are batched, reviewed and tested against supported Node versions rather than merged automatically.

Licence compliance in CI

Every push runs a dependency licence check that fails the build on a disallowed licence, and the resulting inventory is served by your instance at /licenses.html.

Current runtime

EmailEngine supports Node.js 20 and later, prebuilt binaries bundle a current Node runtime, and Redis is the only other moving part.

Straight answers

Does EmailEngine come with SOC 2 or ISO 27001?

No. EmailEngine is software you run, so it inherits the compliance posture of your own environment rather than carrying certifications of its own. If your environment is certified, EmailEngine adds no third party to assess, because your users' mail never reaches us. If your buyers require the email vendor itself to hold the certificate, a hosted API is the better fit.

Is encryption at rest on by default?

Only once you set a secret. Field-level encryption of credentials and tokens activates when the EENGINE_SECRET encryption key is configured; without it, stored values are not encrypted. Setting it is the first item in the deployment security checklist, and a built-in CLI re-encrypts existing data when you add or rotate the key.

Who sets security headers like CSP and HSTS?

Your reverse proxy. EmailEngine binds to localhost by default and is designed to run behind Caddy, nginx or a similar proxy that terminates TLS and adds security headers; the documentation ships ready-made configurations. The application does not emit CSP or HSTS headers itself.

How isolated are custom webhook functions?

Webhook filter and transform functions run in an isolated execution context with a restricted set of globals and an enforced timeout, and only administrators can author them. This is an isolation mechanism, not a hardened security sandbox: treat script authoring as an admin-level privilege and never expose it to untrusted users. The documentation says the same thing, in the same words.

Has EmailEngine been independently audited?

There is no published third-party audit report. The full source is available, so your security team can review the code directly rather than taking a summary on trust, and vulnerabilities are handled through a private disclosure process with published advisories.

Which versions receive security fixes?

The latest release only. Fixes are not backported; upgrading to the current release line is the supported way to receive security updates. EmailEngine ships a release every few days, so staying current is the normal operating mode.

Reporting a vulnerability

Please report privately rather than in a public issue. Reports go to the person who wrote the code, and reporters who want credit are named in the advisory.

GitHub Security Advisories

The preferred channel: open a private report on GitHub. The discussion stays private until a fix ships, and accepted issues are disclosed as published advisories.

Email, PGP if you need it

Send details to andris@postalsys.com, encrypted with the published PGP key if the report is sensitive. A signed security.txt carries the same contacts.

Scope, in writing

The security policy lists what is in scope (API and admin authentication, OAuth2 handling, credential encryption, the proxy servers, webhook delivery, exports) and what is not, so you know before you start digging.

Audit it yourself

The strongest claim on this page is that none of it has to be taken on trust. Read the source, then run the trial on your own hardware.