Skip to main content

Deploy EmailEngine on Render.com

Render runs EmailEngine as a Node.js web service with a managed Redis instance next to it. The EmailEngine repository ships a Render blueprint, render.yaml, that provisions both, so the whole deployment can be done from the Render dashboard without a shell.

Blueprint Deploy

Deploy to Render

The button creates the two services described in the blueprint below and generates EENGINE_SECRET for you.

Costs: two paid services, one web service and one Redis instance. Render revises its plans, so read the current figures on Render's pricing page rather than budgeting from a number quoted here.

What the Blueprint Provisions

render.yaml in the EmailEngine repository declares the following. The values are the ones the file carries at v2.79.4; the file itself is the reference if the two differ.

Web service emailengine:

SettingValueNotes
RuntimenodeBuilds from the GitHub repository
Planpro (standard for preview environments)Pick the size from Render's current plan list; see System Requirements for what EmailEngine needs
Instances1EmailEngine does not support more than one instance per Redis database
Build commandnpm install --omit=dev
Pre-deploy command./update-info.shRecords the build version shown in the dashboard
Start commandnpm start
Auto-deployoffA push to the repository does not redeploy the service
Health check pathnot set/health is present in the file but commented out; see Health Checks

Redis instance ee-redis:

SettingValueNotes
Planstandard
maxmemoryPolicynoevictionRequired. Under the Render default, allkeys-lru, Redis silently drops EmailEngine's data when memory runs out
ipAllowListemptyReachable only from services in the same Render account

Environment variables set on the web service:

KeyValueNotes
EENGINE_REDISconnection string of ee-redisFilled in by Render from the Redis service
EENGINE_SECRETgeneratedA new value is generated each time the blueprint creates the service; see Backup
EENGINE_WORKERS2IMAP worker threads
EENGINE_TIMEOUT30sHow long an IMAP command may take before the API request is aborted
EENGINE_HOST0.0.0.0Render reaches the service over the container network, so it must not bind to localhost
EENGINE_API_PROXY1Render terminates TLS and forwards X-Forwarded-For; this makes EmailEngine read the client address from it
EENGINE_MAX_SIZE25MLargest attachment accepted on submit and upload

Every variable is described in Environment Variables.

Manual Setup

The same deployment can be assembled by hand. The screenshots below are from an earlier Render dashboard; the labels have moved since, the settings have not.

Step 1: Create the Redis Instance

  1. Log in at dashboard.render.com and choose New > Redis

    Create Redis

  2. Configure it:

    SettingValue
    Nameemailengine-redis, or any name
    RegionThe region the web service will use
    Maxmemory policynoeviction

    Redis Configuration

  3. Create it, wait for it to start, and copy the Internal Connection String from its info page. It has the form redis://red-xxxxxxxx:6379.

    Redis URL

The internal connection string keeps Redis traffic inside Render's network. The external string is only needed for a connection from outside Render, such as the backup below.

Step 2: Create the Web Service

  1. Choose New > Web Service

    Create Web Service

  2. Point it at the public repository https://github.com/postalsys/emailengine

    Connect Repository

  3. Configure it:

    SettingValue
    Nameemailengine, or any name
    RegionSame as the Redis instance
    Branchmaster, or a release tag such as v2.79.4
    RuntimeNode
    Build commandnpm install --omit=dev
    Start commandnpm start
  4. Add the environment variables from the blueprint table. Paste the Redis internal connection string as EENGINE_REDIS and generate EENGINE_SECRET yourself:

    openssl rand -hex 32

    Environment Variables

  5. Create the service

Step 3: Wait for the Deployment

The first deploy clones the repository, installs dependencies and starts the service. The Logs tab shows progress; the service is up once this line appears:

{"level":30,"msg":"Started API server thread","port":3000,"host":"0.0.0.0"}

The service URL, https://<name>.onrender.com, is shown at the top of the service page. Opening it shows the EmailEngine admin interface.

Deployed Application

After the First Deploy

Three things are not part of the blueprint and are done in the admin interface once it is up:

  1. Set an admin password. Until one is set, the admin interface opens without a login, and it refuses to issue access tokens. Open Account > Security from the username menu in the top-right corner, or set EENGINE_PREPARED_PASSWORD as described in Prepared Admin Password
  2. Set the Service URL under Configuration > General to the public URL of the service. OAuth2 callbacks, hosted authentication forms and passkeys are all built from it
  3. Add the license under Configuration > License, or pass it as EENGINE_PREPARED_LICENSE. Without one the instance runs on the trial

Custom Domain

  1. Open Settings > Custom Domains on the web service and add the domain
  2. Create the DNS record Render shows, a CNAME pointing at the onrender.com hostname
  3. Render provisions the certificate once the record resolves

Update the Service URL in EmailEngine afterwards, since OAuth2 redirect URLs registered at Google or Microsoft carry the hostname.

Changing Environment Variables

Edit them on the Environment tab. Saving redeploys the service. Two are commonly added after the first deploy:

# License key, pasted as one line
EENGINE_PREPARED_LICENSE="<your-encoded-license>"

# Settings applied at startup, as JSON. This one turns on webhook delivery for every event
EENGINE_SETTINGS='{"webhooks":"https://your-app.example.com/webhooks","webhookEvents":["*"],"webhooksEnabled":true}'

OAuth2 applications and webhook targets are settings rather than individual environment variables: configure them in the admin interface, through the settings API, or through EENGINE_SETTINGS as above. See Prepared Settings.

Health Checks

EmailEngine serves GET /health without authentication. It returns {"success":true} when every configured IMAP worker thread is running and a Redis write-read-delete round trip succeeds, and 500 otherwise.

The blueprint leaves healthCheckPath commented out, so Render only checks that the port answers. To have Render restart the service on a failing check, enable it on the Settings tab or uncomment the line in your copy of the blueprint:

services:
- type: web
name: emailengine
runtime: node
healthCheckPath: /health
buildCommand: npm install --omit=dev
startCommand: npm start

Scaling

Vertical Only

Move to a larger instance type under Settings > Instance Type; the service redeploys. EmailEngine keeps one IMAP connection open per account, so memory grows with the account count. Watch the Metrics tab and upgrade when memory stays high.

No Horizontal Scaling

Keep the instance count at one. Two EmailEngine instances on the same Redis database each sync every account and compete for the same state. The blueprint pins numInstances: 1 for this reason. See Performance Tuning.

Redis Size

Redis holds all of EmailEngine's state, and with noeviction it refuses writes rather than dropping data when full. Upgrade the Redis plan before memory usage approaches the limit; the Redis service page shows current usage.

Monitoring

Logs

The Logs tab streams the JSON lines EmailEngine writes to stdout. EENGINE_LOG_LEVEL on the web service controls verbosity; see Logging.

Prometheus Metrics

GET /metrics on the service URL serves Prometheus metrics to a token that holds the metrics scope. Create the token under Integrations > Access Tokens in the admin interface, then:

curl -H "Authorization: Bearer YOUR_TOKEN" https://emailengine.example.com/metrics

See Monitoring for the metrics exposed.

Render Alerts

Render notifies on deploy failures and, when a health check path is set, on failed checks. Configure recipients under Settings > Notifications on the service.

Backup and Recovery

Everything EmailEngine knows lives in two places: the Redis database, and EENGINE_SECRET, which decrypts the credentials stored there.

Keep a copy of EENGINE_SECRET. The blueprint generates it with generateValue: true, so a service recreated from the blueprint gets a new secret and can no longer read the credentials in the old Redis data. Copy the value from the Environment tab and store it with your other secrets.

Back up Redis from a machine outside Render using the external connection string, which requires adding that machine's address to the Redis ipAllowList:

redis-cli -u rediss://red-xxxxxxxx:6379 --rdb ./emailengine-dump.rdb

To restore, create the services again, set EENGINE_SECRET to the saved value rather than letting Render generate one, load the RDB file into the new Redis instance, and point DNS at the new service.

See Also