Skip to main content

Email Hosting

Kuploy Cloud includes built-in email hosting powered by Stalwart Mail Server. This allows your organizations to create mailboxes on their own custom domains (e.g., user@company.com).

Overview

Email hosting is a plan-gated feature. Your kuploy.app subscription determines whether email hosting is available and how many mailboxes each plan allows:

PlanFeature EnabledMailbox Limit
HobbyNo0
StarterYes10
GrowthYes50
BusinessYes200
EnterpriseYesUnlimited

Mailbox counts are tracked per-instance and reported to kuploy.app during license sync. The same quota enforcement (80% warning, 100% hard block) applies as with other resources.

Architecture

┌─────────────┐     SMTP/IMAP     ┌───────────────────┐
│ Mail Client │ ◄──────────────► │ Stalwart Mail │
│ (Thunderbird,│ │ Server │
│ Outlook) │ │ (ports 25,587, │
└─────────────┘ │ 993,4190) │
└─────────┬─────────┘
│ REST API
┌─────────▼─────────┐
│ kuploy-cloud │
│ (provisioning) │
└─────────┬─────────┘
│ heartbeat sync
┌─────────▼─────────┐
│ kuploy.app │
│ (quota tracking) │
└───────────────────┘
  • Stalwart handles SMTP (sending/receiving), IMAP (mailbox access), and Sieve (filtering).
  • kuploy-cloud provisions accounts and domains via Stalwart's REST API.
  • kuploy.app tracks mailbox counts as part of the license quota system.

Setup

1. Deploy Stalwart

Stalwart runs as a StatefulSet alongside your kuploy-cloud instance. Deploy it using the kuploy-k8s deployer:

python deploy.py stalwart \
--mail-hostname mail.example.com \
--postgres-service pg-postgresql-0.pg-postgresql-headless \
--db-namespace database \
--storage-class longhorn \
--stalwart-lb-ip <dedicated-ip> \
--stalwart-storage 100Gi

Or include it in a full deployment with --mail-hostname:

python deploy.py --edition cloud --domain console.example.com \
--mail-hostname mail.example.com

For Docker Compose deployments, add Stalwart as a service with ports 25, 587, and 993 exposed.

2. Create an API Key

kuploy-cloud communicates with Stalwart via its REST API using an API key (Bearer token).

  1. Access the Stalwart web UI (port-forward if needed: kubectl port-forward svc/stalwart-api -n kuploy 8080:8080)
  2. Log in with the admin password (printed in Stalwart pod logs on first boot)
  3. Navigate to Directory → API Keys → Create API Key
  4. Copy the Bearer token from the Authentication tab (starts with api_)
  5. On the Permissions tab, set all required permissions to On (not Default):
    • Create/Remove/View/Modify principals
    • Add/Remove/View email domains
    • Create/Retrieve DKIM signatures
    • Authenticate
warning

Stalwart API key permissions default to denied. "Default" does not mean inherited from admin. You must explicitly toggle each permission to "On".

3. Configure Connection

Set the API URL and API key in your kuploy-cloud instance. You can use either method:

Option A: Admin Dashboard (recommended)

Go to Admin → Email Hosting (Stalwart) (/admin/mail), enter the API URL and API key, and click Test Connection to verify.

SettingProductionLocal Dev
API URLhttp://stalwart-api.kuploy:8080http://localhost:8080 (with port-forward)
API KeyBearer token from step 2Same

Option B: Environment Variables

VariableDescriptionExample
STALWART_API_URLBase URL of Stalwart's management APIhttp://stalwart-api:8080
STALWART_API_TOKENAPI key (Bearer token from Directory → API Keys)api_abc123...

Admin dashboard settings take precedence over environment variables when configured.

The mail admin page also shows a Live Server Status card that pings Stalwart every 30 seconds and reports reachability, latency, version, and queue depth. Use it to confirm the connection is healthy before provisioning mailboxes.

tip

Without these settings, the mailbox UI will still work for managing records locally, but no actual email delivery will occur. This lets you pre-configure domains and mailboxes before Stalwart is ready.

Token decryption fails after sharing the database

If you run two deployments (e.g. prod and dev) against the same database, both must use the same APP_SECRET. The Stalwart API token is encrypted with APP_SECRET; if the secret differs, the second deployment cannot decrypt it and the mail page shows a "token decryption failed" alert. Either align APP_SECRET across deployments or re-save the configuration on each deployment to re-encrypt with the local key.

4. DNS Configuration

Each mail domain requires the following DNS records. The kuploy-cloud dashboard shows the exact records needed when a domain is added:

RecordHostValue
MXexample.com10 mail.your-instance.kuploy.cloud
TXTexample.comv=spf1 a mx include:mail.your-instance ~all
TXTkuploy._domainkey.example.comv=DKIM1; k=rsa; p=<public-key>
TXT_dmarc.example.comv=DMARC1; p=quarantine; rua=mailto:postmaster@example.com

After adding records, use the Verify button in the dashboard to check DNS propagation.

5. Firewall / Load Balancer

Ensure the following ports are accessible from the internet:

PortProtocolPurpose
25TCPSMTP (receiving)
587TCPSMTP Submission
993TCPIMAPS (mailbox access)

Quota Enforcement

Mailbox limits work the same as other resources:

  1. License-level (aggregate) — kuploy.app enforces the total across all instances
  2. Org-level (per-plan) — The organization's assigned plan template sets a per-org cap
  3. Warnings at 80%, hard block at 100%
  4. Usage notifications are sent via your configured notification channels (email, Slack, Discord, etc.)

Plan Template Configuration

When creating plan templates for your customers, you can set the mailboxes limit and mailboxes feature flag:

  • Feature flag (mailboxes: true) — enables the Email Hosting section in the customer dashboard
  • Limit (mailboxes: N) — maximum number of mailboxes the organization can create
  • Set mailboxes: -1 for unlimited

These are configured in the Billing > Plan Templates section of the kuploy.app dashboard.

Monitoring

Mailbox counts appear in:

  • kuploy.app Billing page — as a usage ring alongside other resources
  • License sync warnings — when approaching 80% or 90% of the mailbox limit
  • Per-org usage — visible in the Organizations section of the kuploy.app dashboard