Self-Hosting

Reverse Proxy & HTTPS

Delivr's services speak plain HTTP on localhost. A reverse proxy in front of them terminates TLS, serves both domains, and is the only thing exposed to the internet.

All examples use:

DomainForwards to
mail.example.comDelivr Web — 127.0.0.1:14128
api.mail.example.comDelivr API — 127.0.0.1:14123

What the proxy must do

  • Serve HTTPS. Browsers only install PWAs and keep secure cookies on HTTPS origins.
  • Allow large request bodies on the API. Sending a mail with attachments can be up to DLA_MAX_ATTACHMENT_SIZE_MB + 16 MB — 41 MB with the defaults. Many proxies default to 1 MB.
  • Allow slow responses. Some IMAP operations (large folders, big attachments, slow mail servers) take longer than typical web requests. A read timeout of 120 seconds is a safe choice.
  • Forward the client address. Login rate limiting needs to tell clients apart. All examples below pass it on in X-Forwarded-For — set DLA_TRUST_PROXY=true on the API so Delivr uses it.
  • Not cache API responses. Delivr sends Cache-Control: no-store for attachments; don't override it.

Configurations

Caddy obtains and renews certificates automatically — this is the shortest working setup.

/etc/caddy/Caddyfile
mail.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:14128
}

api.mail.example.com {
    request_body {
        max_size 50MB
    }
    # Caddy has no proxy timeout by default, so slow IMAP operations are fine.
    reverse_proxy 127.0.0.1:14123
}

Reload with sudo systemctl reload caddy.

If you raise DLA_MAX_ATTACHMENT_SIZE_MB, raise the proxy's body limit too. The rule of thumb: proxy limit ≥ attachment limit + 16 MB.

Verify the setup

Check that the API answers over HTTPS:

curl https://api.mail.example.com/health
{
  "success": true,
  "code": 200,
  "message": "Delivr API is running",
  "data": null
}

Then open https://mail.example.com. You should see the Delivr sign-in page. If the page loads but signing in fails, jump to Troubleshooting — it's almost always a mismatch between DLA_APP_URL and the web client's URL.

Single domain (advanced)

If you can only use one domain, you can serve the API under a path such as /api and strip that prefix in the proxy. The browser then talks to the same origin, so no cross-origin requests are involved.

/etc/caddy/Caddyfile
mail.example.com {
    handle_path /api/* {
        request_body {
            max_size 50MB
        }
        reverse_proxy 127.0.0.1:14123
    }
    handle {
        reverse_proxy 127.0.0.1:14128
    }
}

Then configure:

  • NUXT_PUBLIC_API_URL=https://mail.example.com/api/v1
  • NUXT_PUBLIC_APP_URL=https://mail.example.com
  • DLA_APP_URL=https://mail.example.com
handle_path removes the /api prefix before forwarding, so the API still receives /v1/…. Don't forward the prefix — the API doesn't know about it. The interactive API reference also expects to live at the root of its domain, so with this layout it won't load correctly; set DLA_DISABLE_DOCS=true or use a separate API domain if you need it.

Next steps