mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(email): configurable reply-to address (#3140)
This commit is contained in:
@@ -163,6 +163,7 @@ function defaultConfig(): MasterConfig {
|
||||
provider: 'none',
|
||||
from_email: '',
|
||||
from_name: 'Fluxer',
|
||||
reply_to_email: '',
|
||||
app_base_url: '',
|
||||
},
|
||||
voice: {
|
||||
@@ -292,6 +293,12 @@ function requireString(value: string | undefined, envName: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function validateReplyToEmail(value: string): void {
|
||||
if (value !== '' && !/^[^\s@<>,;"]+@[^\s@<>,;"]+$/.test(value)) {
|
||||
throw new Error('FLUXER_EMAIL_REPLY_TO_EMAIL must be a single email address such as [email protected]');
|
||||
}
|
||||
}
|
||||
|
||||
function validateUploadRelaySecret(value: string): void {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.length === 0) {
|
||||
@@ -566,6 +573,7 @@ function normalizeConfig(config: MasterConfig): MasterConfig {
|
||||
validateStorageChangeFeedConfig(config);
|
||||
validateCachePurgeConfig(config);
|
||||
validateStoreBillingConfig(config);
|
||||
validateReplyToEmail(config.integrations.email.reply_to_email);
|
||||
normalizeAppOriginAliases(config);
|
||||
assertIntegerInRange(config.services.api.max_inflight_requests, 'FLUXER_API_MAX_INFLIGHT_REQUESTS', 1, 100_000);
|
||||
assertIntegerInRange(config.services.api.headers_timeout_ms, 'FLUXER_API_HEADERS_TIMEOUT_MS', 1_000, 3_600_000);
|
||||
|
||||
@@ -169,6 +169,7 @@ export interface MasterConfig {
|
||||
provider: 'smtp' | 'none';
|
||||
from_email: string;
|
||||
from_name: string;
|
||||
reply_to_email: string;
|
||||
app_base_url: string;
|
||||
webhook_secret?: string;
|
||||
smtp?: {
|
||||
|
||||
@@ -290,6 +290,30 @@ describe('ConfigLoader', () => {
|
||||
expect(config.services.api.storage_change_feed?.skip_buckets).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads the email reply-to address', async () => {
|
||||
stubMinimalEnv({FLUXER_EMAIL_REPLY_TO_EMAIL: '[email protected]'});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.email.reply_to_email).toBe('[email protected]');
|
||||
});
|
||||
|
||||
test('leaves the email reply-to address empty when unset or blank', async () => {
|
||||
stubMinimalEnv({FLUXER_EMAIL_REPLY_TO_EMAIL: ' '});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.email.reply_to_email).toBe('');
|
||||
});
|
||||
|
||||
test.each(['support', 'Support <[email protected]>', '[email protected],[email protected]', ' [email protected]'])(
|
||||
'rejects %j as the email reply-to address',
|
||||
async (value) => {
|
||||
stubMinimalEnv({FLUXER_EMAIL_REPLY_TO_EMAIL: value});
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_EMAIL_REPLY_TO_EMAIL must be a single email address');
|
||||
},
|
||||
);
|
||||
|
||||
test('keeps explicit passkey relying party values', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_BASE_DOMAIN: 'chat.example.com',
|
||||
|
||||
@@ -52,6 +52,7 @@ describe('buildNamedFluxerEnvOverrides', () => {
|
||||
FLUXER_S3_FORCE_PATH_STYLE: 'true',
|
||||
FLUXER_AUTH_BLUESKY_KEYS: '[{"kid":"key-1","private_key_path":"/etc/fluxer/keys/bluesky.pem"}]',
|
||||
FLUXER_STRIPE_PRICE_MONTHLY_USD: 'price_monthly_usd',
|
||||
FLUXER_EMAIL_REPLY_TO_EMAIL: '[email protected]',
|
||||
});
|
||||
|
||||
expect(overrides).toMatchObject({
|
||||
@@ -62,7 +63,10 @@ describe('buildNamedFluxerEnvOverrides', () => {
|
||||
bluesky: {keys: [{kid: 'key-1', private_key_path: '/etc/fluxer/keys/bluesky.pem'}]},
|
||||
},
|
||||
s3: {force_path_style: true},
|
||||
integrations: {stripe: {prices: {monthly_usd: 'price_monthly_usd'}}},
|
||||
integrations: {
|
||||
stripe: {prices: {monthly_usd: 'price_monthly_usd'}},
|
||||
email: {reply_to_email: '[email protected]'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,6 +81,7 @@ describe('buildNamedFluxerEnvOverrides', () => {
|
||||
FLUXER_LIVEKIT_DEFAULT_REGION: blank,
|
||||
FLUXER_AUTH_BLUESKY_KEYS: blank,
|
||||
FLUXER_EMAIL_FROM_NAME: blank,
|
||||
FLUXER_EMAIL_REPLY_TO_EMAIL: blank,
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
@@ -148,6 +148,7 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
|
||||
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_REPLY_TO_EMAIL: {path: ['integrations', 'email', 'reply_to_email']},
|
||||
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']},
|
||||
|
||||
Reference in New Issue
Block a user