Skip to main content

EmailEngine CLI

The EmailEngine command line interface manages tokens, licenses, passwords, accounts, and encryption. It talks to Redis directly, so it can run anywhere that can reach the database, whether or not the server is running.

Overview​

Remote Administration​

The EmailEngine CLI does not need to run on the same server as EmailEngine. You can run CLI commands from any location as long as you can:

  1. Access the Redis instance used by EmailEngine
  2. Know the encryption secret (for some operations)

This enables:

  • Remote administration
  • Automated deployment scripts
  • CI/CD pipeline integration
  • Backup and migration tools

Installation​

The EmailEngine CLI can be installed in several ways:

Install globally from npm:

npm install -g emailengine-app

After installation, the emailengine command is available system-wide:

emailengine --version
emailengine help tokens

Option 2: Download Binary​

Download a pre-built binary from the installation guide:

Available formats:

  • Compiled binaries (Linux, macOS, Windows)
  • Docker images
  • Source distribution

The binary is the emailengine command and can be run directly:

./emailengine [command] [options]

Option 3: From Source​

If running from source code, install globally from the project directory:

# From the EmailEngine directory
npm install -g .

Getting Help​

EmailEngine provides a dynamic help system that adapts to your terminal width:

# Show all available commands
emailengine --help
emailengine -h
emailengine help

View command-specific help:

emailengine help tokens
emailengine help license
emailengine help check-bounce

The --help flag is only recognized on its own. emailengine tokens --help runs the tokens command without a subcommand, so use the help <command> form. Help is printed on stderr. An unknown command is refused with Unknown command and exit status 1 rather than starting the server.

One command is not listed by the help output: emailengine webhooks prints the webhook event table that the EmailEngine README carries, and exits with status 1. It is a maintenance helper and needs no Redis connection.

Version Information​

emailengine --version
emailengine -v
emailengine version

Output:

EmailEngine v2.82.0 (LICENSE_EMAILENGINE)

Configuration Arguments​

Essential Arguments​

Redis Connection​

Required for most commands:

--dbs.redis="redis://host:port/db"

Examples:

# Local Redis, database 0
--dbs.redis="redis://127.0.0.1:6379/0"

# Remote Redis with password
--dbs.redis="redis://:password@remote.example.com:6379/8"

# Redis with username and password
--dbs.redis="redis://user:password@remote.example.com:6379/8"

# TLS connection (Redis over TLS)
--dbs.redis="rediss://user:password@remote.example.com:6380/8"

Important: Use the same Redis database number as your EmailEngine instance. EmailEngine accepts only redis:// and rediss:// (TLS) connection strings - Redis Sentinel and Redis Cluster connection URLs are not supported.

Encryption Secret​

Required for encryption-related operations:

--service.secret="your-encryption-secret"

When needed:

  • Account export with encrypted fields
  • Encryption migration (encrypt command)

Not needed for:

  • Token operations (tokens are stored as hashes, not encrypted)
  • License operations
  • Password operations

Commands​

Run EmailEngine Server​

Start the EmailEngine application:

emailengine

With custom configuration:

emailengine \
--dbs.redis="redis://127.0.0.1:6379/8" \
--api.port=3000 \
--api.host="0.0.0.0" \
--workers.imap=8 \
--log.level="info"

Common options:

OptionEnvironment VariableDescriptionDefault
--dbs.redisEENGINE_REDISRedis connection URLredis://127.0.0.1:6379/8
--api.hostEENGINE_HOSTAPI server host127.0.0.1
--api.portEENGINE_PORTAPI server port3000
--workers.imapEENGINE_WORKERSAccount worker count4
--workers.webhooksEENGINE_WORKERS_WEBHOOKSWebhook worker count1
--log.levelEENGINE_LOG_LEVELLog leveltrace
--service.secretEENGINE_SECRETEncryption secretNone

All Server Arguments​

emailengine --help lists the options below, in the groups the help output uses. The worker, queue, API body and TLS options joined the listing in v2.79.8; before that --help showed a subset of what the code reads.

General

OptionDescriptionDefault
--dbs.redisRedis connection URLredis://127.0.0.1:6379/8
--workers.imapAccount worker threads. cpus uses one per CPU core4
--settingsPre-configured settings as a JSON stringNone
--service.secretKey for encrypting stored credentialsNone
--service.commandTimeoutMaximum time for an IMAP command10s
--service.setupDelayDelay between assigning connections to workers0ms
--log.levelLogging leveltrace
--log.rawLog raw IMAP traffic. Includes unmasked credentialsfalse
--workers.webhooksWebhook worker threads1
--workers.submitSubmission worker threads1
--workers.exportExport worker threads. The one worker count with no environment variable1
--workers.apiAPI worker threads. Anything above 1 needs SO_REUSEPORT, so it falls back to 1 off Linux. cpus is accepted1

API server

OptionDescriptionDefault
--api.hostBind address127.0.0.1
--api.portPort3000
--api.maxSizeMaximum attachment size when submitting or uploading5M
--api.maxBodySizeMaximum request body for message uploads50M
--api.maxPayloadTimeoutTime allowed to receive a request body10s
--api.proxyTrust X-Forwarded-For from the reverse proxy in front of the API. Seeds the Behind Reverse Proxy setting on first start; when the argument, the config key and EENGINE_API_PROXY are all absent, the setting starts out enabledUnset
--api.tls.certPathPath to the TLS certificate. Setting it serves the API and admin interface over HTTPSNone
--api.tls.keyPathPath to the TLS private keyNone

Background tasks

OptionDescriptionDefault
--queues.notifyConcurrent webhook deliveries1
--queues.submitConcurrent email submissions1
--queues.exportConcurrent exports per export worker1

SMTP server

OptionDescriptionDefault
--smtp.enabledEnable the SMTP submission serverfalse
--smtp.secretShared SMTP password accepted for all accountsNone
--smtp.hostBind address127.0.0.1
--smtp.portPort2525
--smtp.proxyAccept the HAProxy PROXY protocolfalse
--smtp.maxMessageSizeMaximum accepted email size25M

MCP

OptionDescriptionDefault
--mcp.enabledRegister the MCP endpoint routes. A deployment gate, not the on switch: the mcpEnabled setting still has to be turned ontrue

Every configuration key follows the same --section.key=value form, including keys that --help does not list. The ones EmailEngine reads that are not shown above:

OptionEnvironment VariableDescriptionDefault
--submitDelayEENGINE_SUBMIT_DELAYPause between submissionsNone
--service.fetchBatchSizeEENGINE_FETCH_BATCH_SIZEMessages per fetch batch during sync1000
--licensePathnonePath to a license key file to load and verify at startup. A file that fails verification stops EmailEngine with exit status 13None
--preparedLicense, --preparedToken, --preparedPasswordEENGINE_PREPARED_LICENSE, EENGINE_PREPARED_TOKEN, EENGINE_PREPARED_PASSWORDThe prepared settings in argument formNone
--api.cspModeEENGINE_CSP_MODEHow the Content-Security-Policy is delivered: enforce, report-only or off. Since v2.79.9enforce
--cors.originEENGINE_CORS_ORIGINAllowed CORS originsNone
--cors.maxAgeEENGINE_CORS_MAX_AGECORS preflight cache time60s
--smtp.maxClientsEENGINE_SMTP_MAX_CLIENTSConcurrent connections the SMTP server accepts. Since v2.79.9100
--imap.maxClientsEENGINE_IMAPPROXY_MAX_CLIENTSConcurrent connections the IMAP proxy accepts. The section is imap, not imap-proxy. Since v2.79.91000
--imap-proxy.enabled, --imap-proxy.host, --imap-proxy.port, --imap-proxy.secret, --imap-proxy.proxyEENGINE_IMAP_PROXY_ENABLED, EENGINE_IMAP_PROXY_HOST, EENGINE_IMAP_PROXY_PORT, EENGINE_IMAP_PROXY_SECRET, EENGINE_IMAP_PROXY_PROXYThe IMAP proxy listener, with the same meaning as the SMTP server optionsfalse, 127.0.0.1, 2993, none, false

The IMAP proxy runs as a single worker started by the imapProxyServerEnabled setting; there is no worker count for it.

Environment Variables

The Environment Variables reference lists every variable with its default and the config-file equivalent, including the ones that have no CLI form.


Configuration Files​

EmailEngine supports TOML configuration files for persistent settings.

Using Configuration Files​

Create a TOML file:

# /etc/emailengine/config.toml

[dbs]
redis = "redis://localhost:6379/8"

[api]
host = "0.0.0.0"
port = 3000

[log]
level = "info"

[service]
secret = "your-encryption-secret"

[workers]
imap = 8
webhooks = 2
submit = 2

Load the configuration:

emailengine --config=/etc/emailengine/config.toml

Complete Configuration Example​

# /etc/emailengine/production.toml

# Database configuration
[dbs]
redis = "redis://redis.example.com:6379"

# API server configuration
[api]
host = "0.0.0.0"
port = 3000
proxy = true
maxSize = "20M"

# Worker configuration
[workers]
imap = 8
webhooks = 4
submit = 2

# Logging
[log]
level = "info"

# Service settings
[service]
secret = "your-encryption-secret-32-chars-min"
commandTimeout = "30s"

# IMAP Proxy (optional)
[imap-proxy]
enabled = true
host = "0.0.0.0"
port = 2993
secret = "imap-proxy-secret"

# SMTP Server (optional)
[smtp]
enabled = true
host = "0.0.0.0"
port = 2525
secret = "smtp-server-secret"

Run with config file:

emailengine --config=/etc/emailengine/production.toml

Configuration Precedence​

A CLI argument loses to the matching EENGINE_* environment variable and wins over everything else, so a flag has no effect when the variable is set as well. Configuration Precedence lists all five layers in order.

Example:

# Configuration file has: port = 3000
# CLI argument: --api.port=4000
# Environment variable: EENGINE_PORT=5000

emailengine --config=config.toml --api.port=4000

# Result: Port 5000 (environment variable wins)

Token Management​

Manage API access tokens for authentication.

Issue Token​

Create a new access token:

emailengine tokens issue [options]

Options:

OptionShortDescriptionDefault
--description-dToken descriptionGenerated at <timestamp>
--scope-sOne of *, api, metrics, smtp, imap-proxy, mcp-manage:<level>, mcp:<level>. Repeat the flag for several*
--account-aBind the token to one accountNone
--dbs.redisRedis connectionredis://127.0.0.1:6379/8

An unknown scope is refused with the allowed list, rather than issuing a token that cannot be used.

Examples:

# System-wide token with full access
emailengine tokens issue \
-d "Admin token" \
-s "*" \
--dbs.redis="redis://127.0.0.1:6379/8"

# API-only token
emailengine tokens issue \
-d "API token" \
-s "api" \
--dbs.redis="redis://127.0.0.1:6379/8"

# Account-specific token
emailengine tokens issue \
-d "User token for john@example.com" \
-s "api" \
-a "user123" \
--dbs.redis="redis://127.0.0.1:6379/8"

# Metrics-only token
emailengine tokens issue \
-d "Prometheus metrics" \
-s "metrics" \
--dbs.redis="redis://127.0.0.1:6379/8"

# MCP token that can observe the instance and read mail
emailengine tokens issue \
-d "MCP client" \
-s "mcp-manage:observe" \
-s "mcp:read" \
--dbs.redis="redis://127.0.0.1:6379/8"

Available scopes:

  • "*" - Full access (all operations)
  • "api" - API calls only
  • "metrics" - Prometheus metrics endpoint only
  • "smtp" - SMTP gateway access
  • "imap-proxy" - IMAP proxy access
  • "mcp-manage:<level>" - MCP instance management tools, at the level observe, operate, or administer
  • "mcp:<level>" - MCP email access tools, at the level read, mail, or full

The two MCP scopes give no access to the REST API. Each one requires an access level after a colon, and the token carries the explicit list of grants that level stands for, the same list the admin interface and the MCP consent page mint, so it does not widen when a later release adds tools. Both MCP scopes can be issued in one token, but neither can be combined with a non-MCP scope, because one permissions record covers the whole token; issue separate tokens, or use POST /v1/tokens with an explicit permissions record. Pair an mcp token with -a to bind it to one account. See MCP Access Control for what each level allows.

Before v2.82.1, mcp-manage could not be issued here, and -s mcp issued a token with no permissions record that reached every tool the scope allowed. Since v2.82.1, a scope given without a level, such as -s mcp, is refused, so scripts that issued mcp tokens need a level added.

Output:

The token value, on stdout, with no trailing newline:

f05d76644ea39c4a2ee33e7bffe55808b716a34b51d67b388c7d60498b0f89bc

Export Token​

Export token data for backup or transfer:

emailengine tokens export [options]

Options:

OptionShortDescription
--token-tAccess token to export
--dbs.redisRedis connection

Example:

emailengine tokens export \
-t "f05d76644ea39c4a2ee33e7bffe55808b716a34b51d67b388c7d60498b0f89bc" \
--dbs.redis="redis://127.0.0.1:6379/8"

Output:

The token record, msgpack-encoded and base64url-encoded, on stdout:

hKJpZNlAMzAxZThjNTFhZjgxM2Q3MzUxNTYzYTFlM2I1NjVkYmEzZWJjMzk4ZjI4OWZjNjgzN

Use cases:

  • Backup tokens before migration
  • Transfer tokens between instances
  • Pre-configure tokens via environment variables

Import Token​

Import previously exported token data:

emailengine tokens import [options]

Options:

OptionShortDescription
--token-tExported token data (base64url)
--dbs.redisRedis connection

Example:

emailengine tokens import \
-t "hKJpZNlAMzAxZThjNTFhZjgxM2Q3MzUxNTYzYTFlM2I1NjVkYmEzZWJjMzk4ZjI4OWZjNjgzN" \
--dbs.redis="redis://127.0.0.1:6379/8"

Output:

Token was imported

Important: Use the exported data, not the original token. The same exported value is what EENGINE_PREPARED_TOKEN accepts.


License Management​

Manage EmailEngine licenses.

Show the License Text​

emailengine license

This prints the version on stdout and the EmailEngine license agreement on stderr, so emailengine license 2>/dev/null leaves you with just the version string. It reports the terms EmailEngine is distributed under, not the license key installed on an instance.

For the installed key, read GET /v1/license, which returns whether the license is active, its type, and the details of the key, or open the License page in the admin interface.

Export License​

Export the installed license key for backup or transfer:

emailengine license export --dbs.redis="redis://127.0.0.1:6379/8"

Output:

The key as one base64url string on stdout. When no license is installed the command reports Failed to load license information on stderr and exits with status 1.

eyJsaWNlbnNlIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5

Import License​

Import license key:

emailengine license import [options]

Options:

OptionShortDescription
--license-lThe license key, either the BEGIN LICENSE block or the string from license export
--dbs.redisRedis connection

Example:

emailengine license import \
-l "eyJsaWNlbnNlIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5" \
--dbs.redis="redis://127.0.0.1:6379/8"

Output:

License key was imported

A key that fails verification is reported as Failed to import license information and the command exits with status 1. Both success and failure messages go to stderr.

Use cases:

  • Automated license deployment
  • License updates
  • Migration to new instance

See Prepared License for automated setup.


Password Management​

Manage the admin password for the web interface.

Set Password​

Set or reset the admin password:

emailengine password [options]

Options:

OptionShortDescription
--password-pPassword to set (auto-generated if omitted)
--hash-rPrint the password hash instead of the password
--dbs.redisRedis connection

Examples:

# Set specific password
emailengine password \
-p "MySecurePassword123" \
--dbs.redis="redis://127.0.0.1:6379/8"

# Auto-generate password
emailengine password \
--dbs.redis="redis://127.0.0.1:6379/8"

# Get password hash for prepared settings
emailengine password \
-p "MySecurePassword123" \
-r \
--dbs.redis="redis://127.0.0.1:6379/8"

Auto-generated output:

5b0c9b3e5f0d4a7c8e1f2a3b4c5d6e7f

Hash output (with -r):

JHBia2RmMi1zaGEyNTYkaT02MDAwMDAkMEwwRUVtSkZPNC8vSmtDWlZDTTRTUSRiNCt3QURwWVhOL0ZDUndtMW1QcDdqRkhqR2tjdndxaDBKWFFpS1ZxQTRr

Notes:

  • Minimum 8 characters
  • Auto-generated passwords are 32 hex characters
  • The hash is a PBKDF2-SHA256 string, base64url-encoded, and is what EENGINE_PREPARED_PASSWORD accepts
  • Resetting the password also disables TOTP two-factor authentication and removes every registered passkey

See Reset Password for details.


Account Management​

Export Account​

Export account data including credentials:

emailengine export [options]

Options:

OptionShortDescription
--account-aAccount identifier
--dbs.redisRedis connection
--service.secretEncryption secret (if encryption enabled)

Example:

emailengine export \
-a "user123" \
--dbs.redis="redis://127.0.0.1:6379/8" \
--service.secret="my-encryption-secret"

Output: the account record as JSON, on stdout.

Use cases:

  • Backup specific accounts
  • Migrate accounts between instances
  • Disaster recovery
  • Account auditing
The export is decrypted

Passing --service.secret is what lets the command read the stored values, and what it writes is the decrypted result: the IMAP password or the OAuth2 refresh token in cleartext. Redirect it to a file only where that file is protected the way a credential store would be, and delete it when the migration is done.


Encryption Management​

Manage field-level encryption for stored secrets. One run rewrites every encrypted value the instance holds: the encrypted settings values, account credentials, SMTP gateway passwords, OAuth2 application secrets, the target URL and custom headers of webhook routes, the Let's Encrypt account and domain keys, and the private keys of an uploaded or self-signed TLS certificate.

Encrypt Command​

Migrate encryption settings. This command can be run from any machine with network access to the Redis database - it does not need to run on the EmailEngine server itself.

emailengine encrypt [options]

Options:

OptionDescription
--service.secretNew encryption secret. Leave out to remove encryption
--decryptOld secret for decrypting. Repeat the flag if values were encrypted with different secrets
--dbs.redisRedis connection (required)

Running it with neither option prints the usage text and changes nothing.

Use cases:

  1. Enable encryption (no encryption to encrypted)
  2. Disable encryption (encrypted to plain text)
  3. Re-encrypt (change encryption key)
  4. Migrate from multiple old keys
  5. Rewrite values under the current key derivation (same key, values written by an earlier release)

Examples:

Enable encryption:

emailengine encrypt \
--service.secret="new-encryption-key" \
--dbs.redis="redis://127.0.0.1:6379/8"

Change encryption key:

emailengine encrypt \
--service.secret="new-key" \
--decrypt="old-key" \
--dbs.redis="redis://127.0.0.1:6379/8"

Migrate from multiple old keys:

emailengine encrypt \
--service.secret="new-key" \
--decrypt="old-key-1" \
--decrypt="old-key-2" \
--decrypt="old-key-3" \
--dbs.redis="redis://127.0.0.1:6379/8"

Disable encryption:

emailengine encrypt \
--decrypt="current-key" \
--dbs.redis="redis://127.0.0.1:6379/8"

Rewrite values under the current key derivation:

emailengine encrypt \
--service.secret="current-key" \
--dbs.redis="redis://127.0.0.1:6379/8"

A value whose key was derived with scrypt by an earlier release is rewritten with PBKDF2 even though the key is unchanged; a value already under the current derivation and key is left as it is. This is required before moving an instance to a host in FIPS mode.

The command reports each updated record as it goes (<account>: updated, Gateway <id>: updated, and so on) and finishes each pass with a count, such as Updated 2/3 accounts, Updated 0/1 SMTP gateways, Updated 1/1 OAuth2 apps and Updated 0/0 webhook routes. A value it cannot decrypt with any of the given secrets is reported as Could not process ... Check decryption secrets. on stderr and left as it is.

Important:

  • Back up your data before encryption changes
  • All EmailEngine instances must use the same secret
  • Changing keys requires access to the old key or keys

See Field Encryption for details.


Redis Keyspace Scan​

Scan Command​

Scan Redis and count keys by pattern:

emailengine scan --dbs.redis="redis://127.0.0.1:6379/8"

Output: CSV on stdout with a KEY,COUNT header. Account IDs, hashes, numbers, email addresses, and dates in key names are replaced with placeholders so that keys of the same kind are counted together; progress and the final Checked N keys line go to stderr.

Example output:

KEY,COUNT
"iad:hash(16)",150
"ia:accounts",1
"settings",1
"bull:submit:*",42

Use cases:

  • Audit keyspace structure
  • Capacity planning
  • Debugging

Bounce Email Analysis​

Check Bounce Command​

Analyze an EML file to detect bounce information:

emailengine check-bounce [options]

Options:

OptionShortDescription
--file-fPath to EML file to analyze

Examples:

# Analyze a bounce email
emailengine check-bounce /path/to/bounce.eml

# Using the --file option
emailengine check-bounce --file /path/to/bounce.eml
emailengine check-bounce -f /path/to/bounce.eml

Output: a JSON object with the fields the bounce detector could extract:

  • recipient - The email address that bounced
  • action - The delivery action from the report, for example failed or delayed
  • response.message - The error message from the remote server
  • response.status - Enhanced status code, for example 5.1.1
  • response.source - Where the diagnostic code came from
  • response.category - Classifier label, for example user_unknown or mailbox_full
  • response.recommendedAction - One of remove, retry, retry_different_ip, fix_configuration, review, remove_content
  • response.blocklist and response.retryAfter - Set when the classifier recognized a blocklist listing or a retry hint
  • mta - The mail server that reported the failure
  • queueId - Queue ID from the sending MTA, when present
  • messageId - Message-ID of the original message

Fields that could not be determined are left out. A file that is not a bounce produces {}.

Example output:

{
"recipient": "user@example.com",
"action": "failed",
"response": {
"message": "550 5.1.1 User unknown",
"status": "5.1.1",
"source": "smtp",
"category": "user_unknown",
"recommendedAction": "remove"
},
"mta": "mx.example.com",
"messageId": "<original@example.com>"
}

Use cases:

  • Debug delivery issues
  • Classify bounce types
  • Test bounce detection against a saved message
tip

This command uses the same bounce detection and classification as EmailEngine's automatic bounce handling, so its output matches what a messageBounce webhook would carry for the same message.


Remote Administration Examples​

Scenario 1: Automated Deployment​

Deploy EmailEngine with a pre-configured token:

#!/bin/bash

# Generate token on admin workstation
TOKEN=$(emailengine tokens issue \
-d "Production API token" \
-s "api" \
--dbs.redis="redis://prod-redis.example.com:6379/0")

# Export for prepared settings
PREPARED=$(emailengine tokens export \
-t "$TOKEN" \
--dbs.redis="redis://prod-redis.example.com:6379/0")

# Deploy to production server
ssh prod-server "export EENGINE_PREPARED_TOKEN='$PREPARED' && emailengine"

Scenario 2: CI/CD Pipeline​

# .github/workflows/deploy.yml
steps:
- name: Create deployment token
id: create-token
run: |
TOKEN=$(emailengine tokens issue \
-d "CI deployment token" \
-s "api" \
--dbs.redis="${REDIS_URL}")
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"

- name: Check the account list
env:
EMAILENGINE_TOKEN: ${{ steps.create-token.outputs.token }}
run: |
curl -fsS -H "Authorization: Bearer $EMAILENGINE_TOKEN" \
https://emailengine.example.com/v1/accounts

Scenario 3: Backup Automation​

The CLI has no command that lists tokens or accounts, so the script needs the token values and account IDs as input. Account IDs can come from GET /v1/accounts.

#!/bin/bash
# backup-emailengine.sh

REDIS_URL="redis://backup-source.example.com:6379/0"
BACKUP_DIR="/backups/emailengine/$(date +%Y%m%d)"

mkdir -p "$BACKUP_DIR"

# Export the license
emailengine license export \
--dbs.redis="$REDIS_URL" \
> "$BACKUP_DIR/license.txt"

# Export tokens, one per line in tokens.txt
while read -r TOKEN; do
emailengine tokens export \
-t "$TOKEN" \
--dbs.redis="$REDIS_URL" \
>> "$BACKUP_DIR/tokens.txt"
echo >> "$BACKUP_DIR/tokens.txt"
done < tokens.txt

# Export accounts, one ID per line in accounts.txt
while read -r ACCOUNT; do
emailengine export \
-a "$ACCOUNT" \
--dbs.redis="$REDIS_URL" \
--service.secret="$ENCRYPTION_SECRET" \
> "$BACKUP_DIR/account-$ACCOUNT.json"
done < accounts.txt

Scenario 4: Multi-Instance Management​

Issue a token on several EmailEngine instances:

#!/bin/bash

INSTANCES=(
"redis://instance1.example.com:6379/0"
"redis://instance2.example.com:6379/0"
"redis://instance3.example.com:6379/0"
)

# Each instance gets its own token value
for REDIS_URL in "${INSTANCES[@]}"; do
echo "Creating token on $REDIS_URL"
emailengine tokens issue \
-d "Monitoring token" \
-s "metrics" \
--dbs.redis="$REDIS_URL"
echo
done

Common Patterns​

Pattern 1: Environment-Based Configuration​

# dev.env
REDIS_URL=redis://localhost:6379/8
ENCRYPTION_SECRET=dev-secret

# prod.env
REDIS_URL=redis://prod-redis:6379/0
ENCRYPTION_SECRET=prod-secret-key

# Usage
source dev.env
emailengine tokens issue -d "Dev token" --dbs.redis="$REDIS_URL"

source prod.env
emailengine tokens issue -d "Prod token" --dbs.redis="$REDIS_URL"

Pattern 2: Token Rotation Script​

The CLI can issue tokens but not delete them, so revoke the old one with DELETE /v1/tokens/{token} once the new one is in place.

#!/bin/bash
# rotate-tokens.sh

OLD_TOKEN="$1"
REDIS_URL="$2"

# Create the replacement
NEW_TOKEN=$(emailengine tokens issue \
-d "Rotated token $(date +%Y%m%d)" \
-s "api" \
--dbs.redis="$REDIS_URL")

echo "New token: $NEW_TOKEN"

# Revoke the old one through the API, using the new token
curl -fsS -X DELETE \
-H "Authorization: Bearer $NEW_TOKEN" \
"https://emailengine.example.com/v1/tokens/$OLD_TOKEN"

Pattern 3: Health Check​

/health needs no token, and it checks the things a token would not: that every IMAP worker is up and that Redis answers a write and a read.

#!/bin/bash
# check-emailengine.sh

if curl -fsS https://emailengine.example.com/health > /dev/null; then
echo "EmailEngine is healthy"
exit 0
else
echo "EmailEngine is unhealthy"
exit 1
fi

Minting a token per check would work too, and would leave one behind on every run.

See Also​