Environment Variables

Owlat uses environment variables to configure payments, email sending, authentication, and feature flags. Backend variables are set via the Convex...

Owlat uses environment variables to configure payments, email sending, authentication, and feature flags. Backend variables are set via the Convex dashboard; frontend variables live in .env files.

Two deployment modes

Most readers of this page are self-hosting Owlat. Skip down to Self-hosted configuration — it's the canonical reference for what to put in your .env.

The sections below that (Infrastructure Billing, Hetzner provisioning, tier management) apply only to operators running the hosted cloud — the apps/nest-api control plane. Self-hosters can ignore them; they live behind --profile hosted in docker-compose.yml and are disabled by default.

Self-hosted configuration

Self-hosters put everything in a single .env file at the repo root. Start from the template:

cp .env.selfhost.example .env

The template and docker-compose.yml reference these variables:

Required secrets

VariableDescriptionGenerate with
INSTANCE_SECRETConvex backend signing secret. Also used by the updater sidecar for authenticated calls.openssl rand -hex 32
CONVEX_ADMIN_KEYAdmin key for deploying functions. Generate after first boot via docker compose exec convex ./generate_admin_key.sh.(see above)
MTA_API_KEYAPI key the tenant app uses to enqueue sends in the MTA.openssl rand -base64 32
MTA_WEBHOOK_SECRETHMAC secret for MTA → Convex delivery-event callbacks.openssl rand -base64 32

Public URLs

VariableDescriptionDefault
NUXT_PUBLIC_CONVEX_URLBrowser-facing Convex URL. Must match what you expose via reverse proxy.http://localhost:3210
NUXT_PUBLIC_CONVEX_SITE_URLBrowser-facing Convex HTTP-actions URL.http://localhost:3211
NUXT_PUBLIC_SITE_URLPublic URL of the web app.http://localhost:3000

MTA (mail sending)

VariableDescriptionDefault
EHLO_HOSTNAMEMust match your server's rDNS PTR record.mail.localhost
RETURN_PATH_DOMAINDomain used for VERP bounce return-path addresses.bounces.localhost
IP_POOLS_TRANSACTIONALComma-separated IP(s) for transactional sends.127.0.0.1
IP_POOLS_CAMPAIGNComma-separated IP(s) for marketing campaigns.127.0.0.1
DKIM_KEYSJSON: {"example.com":{"selector":"s1","privateKey":"..."}}.{}
WORKER_CONCURRENCYMax concurrent SMTP deliveries.50
MTA_LOG_LEVELLog verbosity (debug/info/warn/error).info

Deployment mode

VariableDescriptionDefault
OWLAT_DEPLOYMENT_MODEselfhost or hosted. Controls first-run UX, billing visibility, and upgrade prompts in the web app.selfhost
OWLAT_HOSTED_MODEtrue to enable hosted-cloud control-plane features (Stripe, provisioning, tiers). Self-hosters leave this false.false
OWLAT_VERSIONBuild-time version injected into images for in-app update checks. Set automatically by CI.dev

Port overrides (optional)

All ports default to sensible values. Override only if the defaults conflict:

# CONVEX_PORT=3210
# CONVEX_SITE_PORT=3211
# DASHBOARD_PORT=6791
# WEB_PORT=3000
# MTA_HTTP_PORT=3100
# MTA_SMTP_PORT=25
# REDIS_PORT=6379
# CLAMAV_PORT=3310

Optional: PostHog analytics

# NUXT_PUBLIC_POSTHOG_API_KEY=
# NUXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com

The sections below are for hosted-cloud operators only

Everything below this point — Stripe billing, Hetzner provisioning, tier management, the control-plane environment — applies only if you're running the managed cloud offering (not self-hosting). Self-hosters can stop reading here.

Where to Set Variables

Convex backend — tenant app (all process.env references in apps/api/):

npx convex env set VAR_NAME value

Or set them in the Convex dashboard under your deployment's Settings > Environment Variables.

Convex backend — control plane (all process.env references in apps/nest-api/):

The control plane runs its own Convex deployment. Set variables via the Convex dashboard for the nest-api deployment.

Nuxt frontend (apps/web/.env):

NUXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
NUXT_PUBLIC_CONVEX_SITE_URL=https://your-deployment.convex.site
NUXT_PUBLIC_SITE_URL=http://localhost:3000

Infrastructure Billing (Control Plane)

Billing is managed by the control plane (apps/nest-api), not the tenant app. Each organization subscribes to an infrastructure tier that determines their VPS specifications and monthly price.

Pricing Tiers

TierServer TypeSpecsMonthly Price
Startercx222 vCPU, 4 GB RAM, 40 GB disk€28
Growthcx324 vCPU, 8 GB RAM, 80 GB disk€60
Enterprisecx428 vCPU, 16 GB RAM, 160 GB disk€120

Prerequisites

  • A Stripe account
  • Three products with monthly recurring prices (one per tier)

Required Variables

Set these in the nest-api Convex dashboard:

VariableDescription
STRIPE_SECRET_KEYYour Stripe secret API key (starts with sk_).
STRIPE_WEBHOOK_SECRETStripe webhook signing secret (starts with whsec_).
STRIPE_PRICE_STARTERStripe Price ID for the Starter tier (€28/mo).
STRIPE_PRICE_GROWTHStripe Price ID for the Growth tier (€60/mo).
STRIPE_PRICE_ENTERPRISEStripe Price ID for the Enterprise tier (€120/mo).

Setup Steps

  1. Create a Stripe account at stripe.com and note your secret key from the API Keys page.
  2. Create three products in Stripe — one for each tier (Starter, Growth, Enterprise) with monthly recurring pricing. Copy the Price IDs (start with price_).
  3. Set the environment variables in the nest-api Convex dashboard:
    npx convex env set STRIPE_SECRET_KEY sk_live_...
    npx convex env set STRIPE_WEBHOOK_SECRET whsec_...
    npx convex env set STRIPE_PRICE_STARTER price_...
    npx convex env set STRIPE_PRICE_GROWTH price_...
    npx convex env set STRIPE_PRICE_ENTERPRISE price_...
    
  4. Configure a Stripe webhook pointing to the nest-api Convex site URL. Subscribe to events: invoice.paid, invoice.payment_failed, customer.subscription.updated, customer.subscription.deleted.

Infrastructure (Hetzner)

The control plane provisions VPS instances on Hetzner Cloud. These variables are set in the nest-api Convex dashboard.

Required Variables

VariableDescription
HETZNER_API_TOKENHetzner Cloud API token for creating, resizing, and deleting servers.
HETZNER_SSH_KEY_IDSComma-separated Hetzner SSH key IDs to inject into provisioned servers (for emergency access).

Setup Steps

  1. Create a Hetzner Cloud project at console.hetzner.cloud.
  2. Generate an API token with read/write permissions from the project's Security > API Tokens page.
  3. (Optional) Upload an SSH key in the project's Security > SSH Keys page. Copy the key ID.
  4. Set the environment variables:
    npx convex env set HETZNER_API_TOKEN your-api-token
    npx convex env set HETZNER_SSH_KEY_IDS 12345,67890
    

Email Sending

Provider Selection

Owlat supports three email providers: a custom MTA for direct SMTP delivery (default), AWS SES, and Resend. Set the EMAIL_PROVIDER variable in the Convex dashboard:

npx convex env set EMAIL_PROVIDER mta     # default
npx convex env set EMAIL_PROVIDER resend
npx convex env set EMAIL_PROVIDER mta

AWS SES

VariableDescription
AWS_SES_REGIONAWS region for SES (e.g., eu-west-1, us-east-1). Required.
AWS_SES_ACCESS_KEY_IDIAM access key ID with SES permissions. Required.
AWS_SES_SECRET_ACCESS_KEYIAM secret access key. Required.

Setup Steps

  1. Create an IAM user in AWS with programmatic access and the AmazonSESFullAccess policy (or a scoped policy allowing ses:SendEmail and ses:SendRawEmail).
  2. Verify your sending domain in the SES console. SES requires domain verification before you can send emails.
  3. Request production access if your SES account is in sandbox mode. Sandbox mode limits sending to verified addresses only.
  4. Set the environment variables:
    npx convex env set EMAIL_PROVIDER ses
    npx convex env set AWS_SES_REGION eu-west-1
    npx convex env set AWS_SES_ACCESS_KEY_ID AKIA...
    npx convex env set AWS_SES_SECRET_ACCESS_KEY ...
    

Resend

VariableDescription
RESEND_API_KEYResend API key. Required when using Resend. Also used for organization invitation emails.
RESEND_WEBHOOK_SECRETWebhook signing secret (format: whsec_<base64>). Required for delivery event tracking.

Setup Steps

  1. Create a Resend account at resend.com and add your sending domain.
  2. Generate an API key from the Resend dashboard.
  3. (Optional) Set up webhooks for delivery tracking:
    • In Resend, create a webhook pointing to your Convex site URL's webhook endpoint
    • Copy the webhook signing secret
  4. Set the environment variables:
    npx convex env set EMAIL_PROVIDER resend
    npx convex env set RESEND_API_KEY re_...
    npx convex env set RESEND_WEBHOOK_SECRET whsec_...
    

Custom MTA

When using the custom MTA (EMAIL_PROVIDER=mta), the Convex backend connects to the MTA service over HTTP. See the MTA System docs for full architecture details.

VariableDescription
MTA_API_URLMTA service URL (e.g., http://mta.internal:3100). Required when using MTA.
MTA_API_KEYShared API key for MTA authentication (Bearer token). Required when using MTA.
MTA_WEBHOOK_SECRETShared secret for authenticating MTA webhook callbacks. Required when using MTA.

Setup Steps

  1. Deploy the MTA service from apps/mta/ (see the MTA System docs for configuration).
  2. Set the environment variables in the Convex dashboard:
    npx convex env set EMAIL_PROVIDER mta
    npx convex env set MTA_API_URL http://mta.internal:3100
    npx convex env set MTA_API_KEY your-shared-api-key
    npx convex env set MTA_WEBHOOK_SECRET your-webhook-secret
    

Common Email Configuration

These variables apply regardless of which provider you use:

VariableDescriptionDefault
DEFAULT_FROM_EMAILDefault sender email address for transactional emailsnoreply@example.com
DEFAULT_FROM_NAMEDefault sender display nameOwlat
DEFAULT_FROM_DOMAINDomain used for system emails (e.g., invitation emails). Prepended with noreply@.mail.owlat.app
CONVEX_SITE_URLYour Convex site URL. Used for tracking pixels and unsubscribe links in campaign emails.
UNSUBSCRIBE_SECRETSecret key for generating HMAC-signed unsubscribe tokens. Required.

Email Security

Optional environment variables for email content and attachment scanning. See the Email Security docs for details on the scanning pipeline.

Variables

Set in the Convex dashboard:

VariableDescription
GOOGLE_SAFE_BROWSING_API_KEYGoogle Safe Browsing API v4 key for URL reputation checking. Free tier: 10,000 requests/day.
MTA_INTERNAL_URLMTA internal URL for attachment scanning (e.g., http://mta.internal:3100). Required for ClamAV integration.

Set in the MTA environment:

VariableDefaultDescription
CLAMAV_HOSTlocalhostClamAV daemon hostname
CLAMAV_PORT3310ClamAV daemon port

Setup Steps

  1. (Optional) Enable URL reputation checking — get a Google Safe Browsing API key from the Google Cloud Console:
    npx convex env set GOOGLE_SAFE_BROWSING_API_KEY AIza...
    
  2. (Optional) Enable ClamAV malware scanning — deploy ClamAV alongside the MTA (see MTA System > ClamAV Sidecar) and set the internal URL:
    npx convex env set MTA_INTERNAL_URL http://mta.internal:3100
    

Content scanning (spam keywords, phishing URLs, homoglyphs) and file type validation (magic bytes, double extensions) work out of the box with no additional configuration. Only URL reputation and ClamAV require env vars.

Analytics (PostHog)

Owlat integrates with PostHog for product analytics and error tracking. The integration is optional — everything works without it.

Variables

Convex dashboard:

VariableDescription
POSTHOG_API_KEYPostHog project API key (starts with phc_). Required to enable server-side tracking.
POSTHOG_HOSTPostHog instance URL. Defaults to https://eu.i.posthog.com (EU cloud).

Nuxt frontend (apps/web/.env):

VariableDescription
NUXT_PUBLIC_POSTHOG_API_KEYPostHog project API key. Required to enable client-side tracking.
NUXT_PUBLIC_POSTHOG_HOSTPostHog instance URL. Defaults to https://eu.i.posthog.com.

Setup Steps

  1. Create a PostHog project at posthog.com and copy the project API key.
  2. Add the organization group type in PostHog: go to Settings → Groups → add a group type called organization. This enables organization-level analytics.
  3. Set the environment variables:
    # Convex (server-side events)
    npx convex env set POSTHOG_API_KEY phc_...
    npx convex env set POSTHOG_HOST https://eu.i.posthog.com
    
    # apps/web/.env (client-side events)
    NUXT_PUBLIC_POSTHOG_API_KEY=phc_...
    NUXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
    

Tracked Events

Client-side (automatic):

  • $pageview — SPA page navigations
  • $pageleave — page leave events
  • $exception — Vue errors and unhandled promise rejections
  • User identification and organization group association

Server-side (fire-and-forget from mutations):

  • campaign_created, campaign_sent
  • contact_created
  • automation_created, automation_activated, automation_paused
  • topic_created

All server events include organizationId as a property and PostHog group.

Translations (AI)

Owlat supports AI-powered email template translations. OpenRouter is the primary provider; OpenAI is used as a fallback if OpenRouter is not configured.

VariableDescription
OPENROUTER_API_KEYOpenRouter API key for AI translations (primary).
OPENAI_API_KEYOpenAI API key for AI translations (fallback if OpenRouter is not set).

Set in the Convex dashboard:

npx convex env set OPENROUTER_API_KEY sk-or-...
# or, as fallback:
npx convex env set OPENAI_API_KEY sk-...

Authentication

VariableDescriptionDefault
BETTER_AUTH_SECRETSecret key for BetterAuth session signing. Required.
SITE_URLPublic site URL for auth redirects and callbacks.http://localhost:3000

Set BETTER_AUTH_SECRET in the Convex dashboard. Use a long, random string:

npx convex env set BETTER_AUTH_SECRET $(openssl rand -base64 32)

Nuxt Frontend

These go in apps/web/.env:

VariableDescription
NUXT_PUBLIC_CONVEX_URLYour Convex deployment URL (e.g., https://your-deployment.convex.cloud)
NUXT_PUBLIC_CONVEX_SITE_URLYour Convex site URL (e.g., https://your-deployment.convex.site). Used for auth proxy.
NUXT_PUBLIC_SITE_URLPublic site URL. Must match SITE_URL set in Convex.

Admin Dashboard

The nest dashboard (apps/nest/) uses its own .env file. It connects to a separate Convex deployment (apps/nest-api), not the same backend as the tenant web app.

Set in apps/nest/.env:

VariableDescription
NUXT_PUBLIC_CONVEX_URLConvex deployment URL for the nest-api deployment
NUXT_PUBLIC_CONVEX_SITE_URLConvex site URL for the nest-api deployment (used for auth proxy)
NUXT_PUBLIC_SITE_URLAdmin dashboard URL. Defaults to http://localhost:3001.

Complete Reference

Tenant App (apps/api)

VariableWhereRequiredDefaultPurpose
SITE_URLConvexYeshttp://localhost:3000Public site URL for redirects
EMAIL_PROVIDERConvexNomtaEmail provider (mta, ses, or resend)
AWS_SES_REGIONConvexIf SESAWS region for SES
AWS_SES_ACCESS_KEY_IDConvexIf SESAWS IAM access key
AWS_SES_SECRET_ACCESS_KEYConvexIf SESAWS IAM secret key
RESEND_API_KEYConvexIf ResendResend API key
RESEND_WEBHOOK_SECRETConvexNoResend webhook signing secret
MTA_API_URLConvexIf MTAMTA service URL
MTA_API_KEYConvexIf MTAMTA API authentication key
MTA_WEBHOOK_SECRETConvexIf MTAMTA webhook signing secret
DEFAULT_FROM_EMAILConvexNonoreply@example.comDefault sender email
DEFAULT_FROM_NAMEConvexNoOwlatDefault sender name
DEFAULT_FROM_DOMAINConvexNomail.owlat.appDomain for system emails
CONVEX_SITE_URLConvexYesConvex site URL for tracking/unsubscribe
UNSUBSCRIBE_SECRETConvexYesHMAC secret for unsubscribe tokens
BETTER_AUTH_SECRETConvexYesSession signing secret
OPENROUTER_API_KEYConvexNoAI translations (primary provider)
OPENAI_API_KEYConvexNoAI translations (fallback provider)
POSTHOG_API_KEYConvexNoPostHog project API key (server-side)
POSTHOG_HOSTConvexNohttps://eu.i.posthog.comPostHog instance URL (server-side)
GOOGLE_SAFE_BROWSING_API_KEYConvexNoURL reputation checking (Safe Browsing API)
MTA_INTERNAL_URLConvexNoMTA URL for attachment scanning

Control Plane (apps/nest-api)

VariableWhereRequiredDefaultPurpose
BETTER_AUTH_SECRETnest-api ConvexYesSession signing for admin auth
SITE_URLnest-api ConvexYeshttp://localhost:3001Admin dashboard URL for auth redirects
HETZNER_API_TOKENnest-api ConvexYesHetzner Cloud API token for VPS provisioning
HETZNER_SSH_KEY_IDSnest-api ConvexNoComma-separated SSH key IDs for server access
STRIPE_SECRET_KEYnest-api ConvexYesStripe API secret key for subscriptions
STRIPE_WEBHOOK_SECRETnest-api ConvexYesStripe webhook signing secret
STRIPE_PRICE_STARTERnest-api ConvexYesStripe Price ID for Starter tier
STRIPE_PRICE_GROWTHnest-api ConvexYesStripe Price ID for Growth tier
STRIPE_PRICE_ENTERPRISEnest-api ConvexYesStripe Price ID for Enterprise tier

Frontend (apps/web and apps/nest)

VariableWhereRequiredDefaultPurpose
NUXT_PUBLIC_CONVEX_URLNuxt .envYesConvex deployment URL
NUXT_PUBLIC_CONVEX_SITE_URLNuxt .envYesConvex site URL for auth
NUXT_PUBLIC_SITE_URLNuxt .envYesPublic site URL
NUXT_PUBLIC_POSTHOG_API_KEYNuxt .envNoPostHog project API key (client-side)
NUXT_PUBLIC_POSTHOG_HOSTNuxt .envNohttps://eu.i.posthog.comPostHog instance URL (client-side)

MTA (apps/mta)

VariableWhereRequiredDefaultPurpose
CLAMAV_HOSTMTA envNolocalhostClamAV daemon hostname
CLAMAV_PORTMTA envNo3310ClamAV daemon port