Skip to main content

Redis Configuration

EmailEngine requires Redis as its primary data store for mailbox indexes, OAuth credentials, job queues, and webhook events. This guide covers how to configure Redis for EmailEngine and optimize its performance.

Connecting EmailEngine to Redis

Connection String Format

EmailEngine connects to Redis using a connection string specified via the EENGINE_REDIS environment variable or --dbs.redis command-line argument.

Format:

redis://[[username:]password@]host[:port][/database]

Examples:

# Local Redis (default port 6379, default database 8)
EENGINE_REDIS="redis://localhost:6379/8"

# Remote Redis with password
EENGINE_REDIS="redis://:mypassword@redis.example.com:6379"

# Redis with username and password (Redis 6+)
EENGINE_REDIS="redis://admin:mypassword@redis.example.com:6379"

# Redis over TLS
EENGINE_REDIS="rediss://redis.example.com:6380"

The URL is parsed by EmailEngine itself before it reaches the Redis client: the rediss: scheme turns TLS on, the path selects the database, and a username is passed along only when it is not default (add ?allowUsernameInURI=true to force it through). EENGINE_REDIS and --dbs.redis accept a single host; Redis Sentinel and Redis Cluster addresses are not supported.

Deployment Examples

services:
redis:
image: redis:7-alpine
command: redis-server --save 60 1000 --save 300 10 --save 900 1 --maxmemory-policy noeviction
volumes:
- redis-data:/data
ports:
- "6379:6379"
restart: unless-stopped

emailengine:
image: postalsys/emailengine:v2
environment:
- EENGINE_REDIS=redis://redis:6379
- EENGINE_HOST=0.0.0.0
- EENGINE_PORT=3000
ports:
- "3000:3000"
depends_on:
- redis
restart: unless-stopped

volumes:
redis-data:

Connection Options

A few options can be passed as query parameters:

# Connect with TLS
EENGINE_REDIS="rediss://redis.example.com:6380"

# Specify IPv4 or IPv6
EENGINE_REDIS="redis://redis.example.com:6379?family=4" # Force IPv4
EENGINE_REDIS="redis://redis.example.com:6379?family=6" # Force IPv6

# Password and database as parameters instead of URL parts
EENGINE_REDIS="redis://redis.example.com:6379?password=secret&db=8"

family, password, db and allowUsernameInURI are the recognized parameters; anything else in the query string is ignored.

Required Redis Configuration

EmailEngine requires specific Redis settings to function correctly.

Redis Version

Use Redis 6.2 or newer. The queue library EmailEngine is built on (BullMQ) refuses to start against a server older than 5.0 and prints a warning on anything below 6.2. Username-based ACLs in the connection URL need Redis 6.0 or newer.

Memory Eviction Policy (Required)

EmailEngine requires that all keys remain in memory. Set the eviction policy to noeviction:

maxmemory-policy noeviction

Why this matters: If Redis evicts mailbox indexes or OAuth tokens, EmailEngine must resynchronize entire mailboxes, which is expensive and time-consuming.

Verification:

redis-cli CONFIG GET maxmemory-policy

Set at runtime:

redis-cli CONFIG SET maxmemory-policy noeviction

No alternatives: noeviction is the only supported policy. The dashboard shows an "Unsafe Redis eviction policy" banner when any other maxmemory-policy is configured (danger-level when maxmemory is set, warning-level when it is not), and a "Redis eviction detected" danger banner with the evicted key count once Redis has actually evicted keys. The same count is exported as redis_evicted_keys_total on the metrics endpoint.

Enable persistence to prevent data loss on Redis restarts.

# Save after 1 change in 15 minutes
save 900 1

# Save after 10 changes in 5 minutes
save 300 10

# Save after 10000 changes in 1 minute
save 60 10000

Why recommended: RDB creates periodic snapshots with minimal performance impact. Best for EmailEngine's write-heavy workload.

Verification:

redis-cli CONFIG GET save

Configure TCP keep-alive on long-lived connections:

tcp-keepalive 300

Why this matters: Every EmailEngine thread keeps its Redis connections open for the life of the process, and the queue workers hold blocking connections that can sit idle between jobs. Without keep-alive, a NAT device or load balancer between EmailEngine and Redis may drop an idle connection without either side noticing until the next command fails. Redis 3.2 and later default to tcp-keepalive 300 already; the setting only needs attention on an older server or one that has overridden it.

Redis Configuration File Example

Create a redis.conf file with EmailEngine-optimized settings:

# Bind to all interfaces (adjust for security)
bind 0.0.0.0

# Port
port 6379

# Memory limit (adjust based on your needs)
maxmemory 2gb

# Eviction policy - REQUIRED for EmailEngine
maxmemory-policy noeviction

# Persistence - RDB snapshots (RECOMMENDED)
save 900 1
save 300 10
save 60 10000

# Persistence - AOF (NOT RECOMMENDED for EmailEngine)
# Only enable if you have high-performance storage (20,000+ IOPS)
# and understand the performance impact
appendonly no

# TCP keep-alive
tcp-keepalive 300

# Log level
loglevel notice

# Log file
logfile /var/log/redis/redis.log

# Working directory
dir /var/lib/redis

Start Redis with config file:

redis-server /etc/redis/redis.conf

Verifying Connection

Test Redis Directly

# Test connectivity
redis-cli -h localhost -p 6379 ping
# Expected: PONG

Check EmailEngine Logs

EmailEngine uses JSON logging (pino). Log levels: 60=FATAL, 50=ERROR, 40=WARN, 30=INFO, 20=DEBUG, 10=TRACE.

Successful connection:

{"level":30,"time":1762176419767,"pid":93728,"msg":"EmailEngine starting up","version":"2.79.4"}
{"level":30,"time":1762176421071,"pid":93728,"msg":"Started API server thread","port":3000,"host":"127.0.0.1","maxSize":5242880,"maxBodySize":52428800,"version":"2.79.4"}

There is no "Redis connected" message. If "Started API server thread" appears (at info level), the API worker has its Redis connection.

Connection failure: a refused connection, a timeout, a wrong password (NOAUTH or WRONGPASS), or a MISCONF reply before the first successful connection is fatal. EmailEngine prints a boxed message on stderr with the password masked and exits with status 1:

=========================================================================================
Failed to establish connection to Redis using "redis://127.0.0.1:16379"
Can not connect to the database. Redis might not be running. Are you using correct hostname and port values?

To run EmailEngine provide valid Redis configuration
$ emailengine --dbs.redis="redis://username:password@1.2.3.4:6379/0"
=========================================================================================

Other errors before the first connection (an unresolvable hostname, for example) end the same way when EmailEngine runs in a terminal; run as a service, they are logged at warning level while the client keeps retrying. The retry cadence is visible at trace level as Connection retry entries, with the delay doubling from 1 second up to 15 seconds between attempts.

After a connection has been established, a dropped connection is not fatal: the client reconnects on its own, logging Redis connection error at warning level in the meantime.

Pretty format (development):

emailengine | pino-pretty

Data Stored in Redis

Redis is EmailEngine's only database. It holds:

  • Account records, credentials (encrypted when EENGINE_SECRET is set) and connection state
  • The message index for each account: one small entry per message, plus mailbox listings
  • Settings, OAuth2 applications, access token hashes and admin sessions
  • The BullMQ queues: webhook deliveries, the outbox, and export jobs
  • Per-account logs, when enabled

The message index dominates, which is where the 1-2 MiB per account planning figure comes from; see Performance tuning for what drives it. Message bodies and attachments are not stored: they are fetched from the mail server on request.

Check memory usage:

redis-cli INFO memory | grep used_memory_human

Capacity Planning

Memory Sizing

Allocate 1-2 MiB of RAM per account and provision twice the calculated baseline to accommodate:

  • Copy-on-write memory during RDB snapshots
  • Webhook and outbox queue bursts
  • Keep usage below 80% of provisioned memory

Example:

AccountsBase RAMProvisionTarget Usage
100100-200 MiB400 MiB< 320 MiB
1,0001-2 GiB4 GiB< 3.2 GiB
10,00010-20 GiB40 GiB< 32 GiB

Network Latency

Deploy Redis and EmailEngine in the same availability zone. Target RTT < 5ms (ideally < 1ms).

Measure latency:

redis-cli --latency --raw -h redis.example.com

Backups

Back up dump.rdb regularly:

#!/bin/bash
set -euo pipefail
BACKUP_DIR="/backup/redis"
DATE=$(date +%Y%m%d_%H%M%S)

# Remember the previous save so we can tell when this one finished
PREV=$(redis-cli LASTSAVE)
redis-cli BGSAVE
while [ "$(redis-cli LASTSAVE)" = "$PREV" ]; do sleep 1; done

cp /var/lib/redis/dump.rdb "$BACKUP_DIR/dump-$DATE.rdb"
find "$BACKUP_DIR" -name "dump-*.rdb" -mtime +7 -delete

Performance Tuning

Write Pattern

EmailEngine writes one small index entry per message, so the load is dominated by initial syncs: a mailbox with 100,000 messages is 100,000 writes, and several accounts syncing at once multiply that. Steady-state traffic afterwards is a fraction of it.

This is why persistence choice matters here more than it would for a cache, and why RDB snapshots are the recommended setting. Snapshot cost is proportional to dataset size and paid periodically; AOF cost is proportional to write count and paid continuously.

Memory Management

Monitor fragmentation:

redis-cli INFO memory | grep mem_fragmentation_ratio

Target: 1.0-1.5. If > 1.5, restart Redis to defragment:

redis-cli SHUTDOWN SAVE && redis-server /etc/redis/redis.conf

Connection Health

Every EmailEngine thread (the main process, each IMAP worker, the API, webhook and submission workers) opens its own Redis connections, and the queue workers add blocking connections on top, so a single instance normally shows a few dozen clients. The count grows with EENGINE_WORKERS; it should be stable over time.

redis-cli CLIENT LIST | wc -l

The same number is exported as redis_connected_clients on the metrics endpoint.

Managed Redis Services

Compatibility Matrix

ServiceStatusNotes
Upstash RedisSupported with constraintsPer-request size and daily command quotas depend on the plan; an initial sync or a large outbox message can hit them. Deploy in the same region as EmailEngine
Amazon ElastiCacheNot supportedEmailEngine declares ElastiCache incompatible and shows a danger-level dashboard warning - using it as the database backend can result in data loss
Amazon MemoryDBRecognizedDetected from INFO and named on the dashboard; no compatibility warning is raised, and there is no long-term production data
Azure Cache for RedisSupportedPick a tier that offers data persistence and set the eviction policy to noeviction
Google Cloud MemorystoreSupportedUse Standard tier with replication for high availability
Redis CloudSupportedNative Redis service; ensure persistence and eviction policy are configured
MemuraiExperimentalPasses basic tests on Windows; no long-term performance data
DragonflyExperimentalRecognized on the dashboard; validate against production workloads before relying on it
KeyDBExperimentalMulti-threaded fork of Redis; monitor replication lag and memory stability

EmailEngine reads INFO at startup and on the dashboard to recognize these backends, and names the one it found next to the Redis version on the dashboard.

Unsupported: Redis Cluster. EmailEngine detects cluster_enabled and shows a danger-level dashboard warning. Use a single Redis primary with persistence enabled; a replica for failover is fine as long as EmailEngine is given one stable address to connect to.

Service-Specific Configuration

# Upstash connection string format
EENGINE_REDIS="rediss://:YOUR_PASSWORD@YOUR_ENDPOINT.upstash.io:6379"

Limitations:

  • Upstash caps the size of a single request and the number of commands per day by plan; check the current limits against the outbox message sizes and account counts you expect. A free-tier quota is not enough for a production instance
  • Requires same-region deployment to minimize latency

Monitoring

Key Metrics

Memory:

redis-cli INFO memory | grep used_memory_human

Latency:

redis-cli --latency-history -i 1

Persistence:

redis-cli INFO persistence | grep rdb_last_save_time

Alert Thresholds

MetricWarningCritical
Memory usage> 70%> 85%
Memory fragmentation> 1.5> 2.0
Latency (p99)> 10ms> 50ms
Persistence lag> 60s> 300s

EmailEngine Prometheus Metrics

curl https://emailengine.example.com/metrics \
-H "Authorization: Bearer YOUR_METRICS_TOKEN" | grep redis

The endpoint needs a token with the metrics scope.

Example output:

redis_version{version="v7.2.7"} 1
redis_uptime_in_seconds 369345
redis_latency 103542
redis_connected_clients 34
redis_memory_used_bytes 279341568
redis_memory_max_bytes 17179869184
redis_mem_fragmentation_ratio 1.06
redis_instantaneous_ops_per_sec 597
redis_last_save_time 1762178720

Quick Reference

Deploy Redis in the same data center as EmailEngine, allocate sufficient memory, enable RDB persistence, and set eviction policy to noeviction.

Essential checklist:

  • Set maxmemory-policy noeviction
  • Enable RDB persistence (save 60 10000 300 10 900 1)
  • Set tcp-keepalive 300
  • Provision 2× base memory (1-2 MiB per account)
  • Keep usage < 80%
  • Target latency < 5ms
  • Avoid AOF (too high I/O overhead)
  • Avoid Redis Cluster (not supported)

Connection strings:

redis://localhost:6379              # Local
redis://:password@host:6379 # With password
rediss://:password@host:6380 # With TLS
redis://host:6379/8 # Specific database

See Also