diff --git a/deploy/self-hosting/.env.example b/deploy/self-hosting/.env.example index 110fae020..902197514 100644 --- a/deploy/self-hosting/.env.example +++ b/deploy/self-hosting/.env.example @@ -55,6 +55,7 @@ FLUXER_EMAIL_ENABLED=false FLUXER_EMAIL_PROVIDER=none FLUXER_EMAIL_FROM_EMAIL=noreply@example.com FLUXER_EMAIL_FROM_NAME=Fluxer +FLUXER_EMAIL_APP_BASE_URL= FLUXER_EMAIL_SMTP_HOST= FLUXER_EMAIL_SMTP_PORT=587 FLUXER_EMAIL_SMTP_USERNAME= diff --git a/deploy/self-hosting/docker-compose.yml b/deploy/self-hosting/docker-compose.yml index 57fc260ad..940dbb864 100644 --- a/deploy/self-hosting/docker-compose.yml +++ b/deploy/self-hosting/docker-compose.yml @@ -57,6 +57,7 @@ x-fluxer-env: &fluxer-env FLUXER_EMAIL_PROVIDER: ${FLUXER_EMAIL_PROVIDER:-none} FLUXER_EMAIL_FROM_EMAIL: ${FLUXER_EMAIL_FROM_EMAIL:-noreply@localhost} FLUXER_EMAIL_FROM_NAME: ${FLUXER_EMAIL_FROM_NAME:-Fluxer} + FLUXER_EMAIL_APP_BASE_URL: ${FLUXER_EMAIL_APP_BASE_URL:-} FLUXER_EMAIL_SMTP_HOST: ${FLUXER_EMAIL_SMTP_HOST:-} FLUXER_EMAIL_SMTP_PORT: ${FLUXER_EMAIL_SMTP_PORT:-587} FLUXER_EMAIL_SMTP_USERNAME: ${FLUXER_EMAIL_SMTP_USERNAME:-} diff --git a/fluxer_api/src/api/Config.ts b/fluxer_api/src/api/Config.ts index 422a4158e..8f6a8faae 100644 --- a/fluxer_api/src/api/Config.ts +++ b/fluxer_api/src/api/Config.ts @@ -19,6 +19,26 @@ function trimTrailingSlash(url: string): string { return url.replace(/\/+$/u, ''); } +function resolveEmailAppBaseUrl(master: MasterConfig): string { + const configuredAppBaseUrl = master.integrations.email.app_base_url.trim(); + if (!configuredAppBaseUrl) return trimTrailingSlash(master.endpoints.app); + try { + const appBaseUrl = new URL(configuredAppBaseUrl); + if ( + (appBaseUrl.protocol !== 'http:' && appBaseUrl.protocol !== 'https:') || + appBaseUrl.username || + appBaseUrl.password || + appBaseUrl.search || + appBaseUrl.hash + ) { + throw new Error(`Invalid email app base URL: ${configuredAppBaseUrl}`); + } + return trimTrailingSlash(appBaseUrl.toString()); + } catch { + throw new Error(`Invalid email app base URL: ${configuredAppBaseUrl}`); + } +} + function resolveGatewayInternalUrl(master: MasterConfig): string { const configuredInternalGateway = ( master.internal as { @@ -254,6 +274,7 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig { webhookSecret: master.integrations.email.webhook_secret ?? undefined, fromEmail: master.integrations.email.from_email, fromName: master.integrations.email.from_name, + appBaseUrl: resolveEmailAppBaseUrl(master), smtp: master.integrations.email.smtp ? { host: master.integrations.email.smtp.host, diff --git a/fluxer_api/src/api/config/APIConfig.ts b/fluxer_api/src/api/config/APIConfig.ts index 58ea5fd50..487aea7f8 100644 --- a/fluxer_api/src/api/config/APIConfig.ts +++ b/fluxer_api/src/api/config/APIConfig.ts @@ -158,6 +158,7 @@ export interface APIConfig { webhookSecret?: string; fromEmail: string; fromName: string; + appBaseUrl: string; smtp?: { host: string; port: number; diff --git a/fluxer_api/src/api/middleware/ServiceSingletons.ts b/fluxer_api/src/api/middleware/ServiceSingletons.ts index 2613aa09b..727ada566 100644 --- a/fluxer_api/src/api/middleware/ServiceSingletons.ts +++ b/fluxer_api/src/api/middleware/ServiceSingletons.ts @@ -160,7 +160,7 @@ function createEmailServiceForConfig( enabled: emailConfigSource.enabled, fromEmail: emailConfigSource.fromEmail, fromName: emailConfigSource.fromName, - appBaseUrl: Config.endpoints.webApp, + appBaseUrl: emailConfigSource.appBaseUrl, marketingBaseUrl: Config.endpoints.marketing, }; return new EmailService(emailConfig, emailI18n, createEmailProvider(emailConfigSource), bouncedEmailChecker); diff --git a/packages/config/src/ConfigLoader.ts b/packages/config/src/ConfigLoader.ts index e2165d47f..7098be534 100644 --- a/packages/config/src/ConfigLoader.ts +++ b/packages/config/src/ConfigLoader.ts @@ -182,6 +182,7 @@ function defaultConfig(): MasterConfig { provider: 'none', from_email: '', from_name: 'Fluxer', + app_base_url: '', }, sms: { enabled: false, diff --git a/packages/config/src/MasterConfig.ts b/packages/config/src/MasterConfig.ts index 629e55c19..1d7d4cadf 100644 --- a/packages/config/src/MasterConfig.ts +++ b/packages/config/src/MasterConfig.ts @@ -208,6 +208,7 @@ export interface MasterConfig { provider: 'smtp' | 'none'; from_email: string; from_name: string; + app_base_url: string; webhook_secret?: string; smtp?: { host: string; diff --git a/packages/config/src/config_loader/EnvironmentOverrides.ts b/packages/config/src/config_loader/EnvironmentOverrides.ts index dae1be5af..c32f72496 100644 --- a/packages/config/src/config_loader/EnvironmentOverrides.ts +++ b/packages/config/src/config_loader/EnvironmentOverrides.ts @@ -243,6 +243,7 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record = { FLUXER_EMAIL_PROVIDER: {path: ['integrations', 'email', 'provider']}, FLUXER_EMAIL_FROM_EMAIL: {path: ['integrations', 'email', 'from_email']}, FLUXER_EMAIL_FROM_NAME: {path: ['integrations', 'email', 'from_name']}, + FLUXER_EMAIL_APP_BASE_URL: {path: ['integrations', 'email', 'app_base_url']}, FLUXER_EMAIL_WEBHOOK_SECRET: {path: ['integrations', 'email', 'webhook_secret']}, FLUXER_EMAIL_SMTP_HOST: {path: ['integrations', 'email', 'smtp', 'host']}, FLUXER_EMAIL_SMTP_PORT: {path: ['integrations', 'email', 'smtp', 'port'], parse: parseEnvValue},