@voilajsx/appkit
Version:
Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development
400 lines • 16.6 kB
JavaScript
/**
* Smart defaults and environment validation for email with auto-strategy detection
* @module @voilajsx/appkit/email
* @file src/email/defaults.ts
*
* @llm-rule WHEN: App startup - need to configure email strategy and connection settings
* @llm-rule AVOID: Calling multiple times - expensive environment parsing, use lazy loading in get()
* @llm-rule NOTE: Called once at startup, cached globally for performance
* @llm-rule NOTE: Auto-detects Resend → SMTP → Console based on environment variables
*/
/**
* Gets smart defaults using environment variables with auto-strategy detection
* @llm-rule WHEN: App startup to get production-ready email configuration
* @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
* @llm-rule NOTE: Auto-detects strategy: RESEND_API_KEY → Resend, SMTP_HOST → SMTP, default → Console
*/
export function getSmartDefaults() {
validateEnvironment();
const nodeEnv = process.env.NODE_ENV || 'development';
const isDevelopment = nodeEnv === 'development';
const isProduction = nodeEnv === 'production';
const isTest = nodeEnv === 'test';
// Auto-detect strategy from environment
const strategy = detectEmailStrategy();
return {
// Strategy selection with smart detection
strategy,
// Default sender with service identification
from: {
name: process.env.VOILA_EMAIL_FROM_NAME || process.env.VOILA_SERVICE_NAME || 'App',
email: process.env.VOILA_EMAIL_FROM_EMAIL || getDefaultFromEmail(strategy),
},
// Resend configuration (only used when strategy is 'resend')
resend: {
apiKey: process.env.RESEND_API_KEY || '',
baseURL: process.env.RESEND_BASE_URL || 'https://api.resend.com',
timeout: parseInt(process.env.VOILA_EMAIL_RESEND_TIMEOUT || '30000'),
},
// SMTP configuration (only used when strategy is 'smtp')
smtp: {
host: process.env.SMTP_HOST || 'localhost',
port: parseInt(process.env.SMTP_PORT || getDefaultSmtpPort()),
secure: process.env.SMTP_SECURE === 'true' || isDefaultSecurePort(),
auth: {
user: process.env.SMTP_USER || '',
pass: process.env.SMTP_PASS || '',
},
timeout: parseInt(process.env.VOILA_EMAIL_SMTP_TIMEOUT || '30000'),
pool: process.env.SMTP_POOL !== 'false', // Default to true for performance
},
// Console configuration (only used when strategy is 'console')
console: {
colorize: process.env.VOILA_EMAIL_CONSOLE_COLOR !== 'false' && !isProduction,
showPreview: process.env.VOILA_EMAIL_CONSOLE_PREVIEW !== 'false' && isDevelopment,
format: process.env.VOILA_EMAIL_CONSOLE_FORMAT || 'simple',
},
// Environment information
environment: {
isDevelopment,
isProduction,
isTest,
nodeEnv,
},
};
}
/**
* Auto-detect email strategy from environment variables
* @llm-rule WHEN: Determining which email strategy to use automatically
* @llm-rule AVOID: Manual strategy selection - environment detection handles most cases
* @llm-rule NOTE: Priority: RESEND_API_KEY → SMTP_HOST → Console (perfect for dev/prod)
*/
function detectEmailStrategy() {
// Explicit override wins (for testing/debugging)
const explicit = process.env.VOILA_EMAIL_STRATEGY?.toLowerCase();
if (explicit === 'resend' || explicit === 'smtp' || explicit === 'console') {
return explicit;
}
// Auto-detection logic (priority order)
if (process.env.RESEND_API_KEY) {
return 'resend'; // Resend API key available
}
if (process.env.SMTP_HOST) {
return 'smtp'; // SMTP configuration available
}
// Default to console for development/testing
if (process.env.NODE_ENV === 'production') {
console.warn('[VoilaJSX AppKit] No email provider configured in production. ' +
'Using console strategy which will only log emails. ' +
'Set RESEND_API_KEY or SMTP_HOST for production email sending.');
}
return 'console'; // Default to console for development
}
/**
* Get default FROM email based on strategy
* @llm-rule WHEN: No explicit FROM email configured - provides sensible defaults
* @llm-rule AVOID: Hardcoded defaults - strategy-specific defaults work better
*/
function getDefaultFromEmail(strategy) {
switch (strategy) {
case 'resend':
return 'noreply@example.com'; // Resend requires verified domain
case 'smtp':
return process.env.SMTP_USER || 'noreply@localhost';
case 'console':
return 'noreply@localhost';
default:
return 'noreply@localhost';
}
}
/**
* Get default SMTP port based on configuration
* @llm-rule WHEN: No explicit SMTP port configured - uses standard ports
* @llm-rule AVOID: Hardcoded port - security settings affect port choice
*/
function getDefaultSmtpPort() {
const secure = process.env.SMTP_SECURE === 'true';
const host = process.env.SMTP_HOST?.toLowerCase() || '';
// Gmail and common providers use specific ports
if (host.includes('gmail.com')) {
return secure ? '465' : '587';
}
// Standard SMTP ports
return secure ? '465' : '587';
}
/**
* Check if SMTP should use secure connection by default
* @llm-rule WHEN: No explicit SMTP_SECURE setting - auto-detects from port
* @llm-rule AVOID: Insecure connections - defaults to secure when possible
*/
function isDefaultSecurePort() {
const port = parseInt(process.env.SMTP_PORT || '587');
return port === 465; // Port 465 is SSL, 587 is TLS
}
/**
* Validates environment variables for email configuration
* @llm-rule WHEN: App startup to ensure proper email environment configuration
* @llm-rule AVOID: Skipping validation - improper config causes silent email failures
* @llm-rule NOTE: Validates API keys, email addresses, and SMTP settings
*/
function validateEnvironment() {
// Validate email strategy if explicitly set
const strategy = process.env.VOILA_EMAIL_STRATEGY;
if (strategy && !['resend', 'smtp', 'console'].includes(strategy.toLowerCase())) {
throw new Error(`Invalid VOILA_EMAIL_STRATEGY: "${strategy}". Must be "resend", "smtp", or "console"`);
}
// Validate Resend API key format if provided
const resendApiKey = process.env.RESEND_API_KEY;
if (resendApiKey && !resendApiKey.startsWith('re_')) {
throw new Error(`Invalid RESEND_API_KEY format: "${resendApiKey}". Must start with "re_"`);
}
// Validate SMTP configuration if provided
const smtpHost = process.env.SMTP_HOST;
if (smtpHost) {
validateSmtpConfig();
}
// Validate FROM email if provided
const fromEmail = process.env.VOILA_EMAIL_FROM_EMAIL;
if (fromEmail && !isValidEmail(fromEmail)) {
throw new Error(`Invalid VOILA_EMAIL_FROM_EMAIL: "${fromEmail}". Must be a valid email address`);
}
// Validate numeric values
validateNumericEnv('SMTP_PORT', 1, 65535);
validateNumericEnv('VOILA_EMAIL_RESEND_TIMEOUT', 1000, 300000); // 1s to 5min
validateNumericEnv('VOILA_EMAIL_SMTP_TIMEOUT', 1000, 300000); // 1s to 5min
// Validate console format if provided
const consoleFormat = process.env.VOILA_EMAIL_CONSOLE_FORMAT;
if (consoleFormat && !['simple', 'detailed'].includes(consoleFormat)) {
throw new Error(`Invalid VOILA_EMAIL_CONSOLE_FORMAT: "${consoleFormat}". Must be "simple" or "detailed"`);
}
// Production-specific validations
const nodeEnv = process.env.NODE_ENV;
if (nodeEnv === 'production') {
validateProductionConfig();
}
// Validate NODE_ENV
if (nodeEnv && !['development', 'production', 'test', 'staging'].includes(nodeEnv)) {
console.warn(`[VoilaJSX AppKit] Unusual NODE_ENV: "${nodeEnv}". ` +
`Expected: development, production, test, or staging`);
}
}
/**
* Validates SMTP configuration for completeness
* @llm-rule WHEN: SMTP_HOST is configured - ensures complete SMTP setup
* @llm-rule AVOID: Partial SMTP config - causes authentication failures
*/
function validateSmtpConfig() {
const host = process.env.SMTP_HOST;
const user = process.env.SMTP_USER;
const pass = process.env.SMTP_PASS;
if (!host) {
throw new Error('SMTP_HOST is required when using SMTP strategy');
}
// Many SMTP servers require authentication
if (!user && !pass) {
console.warn('[VoilaJSX AppKit] SMTP configured without authentication. ' +
'Set SMTP_USER and SMTP_PASS if your server requires authentication.');
}
if (user && !pass) {
throw new Error('SMTP_PASS is required when SMTP_USER is set');
}
if (!user && pass) {
throw new Error('SMTP_USER is required when SMTP_PASS is set');
}
}
/**
* Validates production email configuration
* @llm-rule WHEN: Running in production environment
* @llm-rule AVOID: Console strategy in production - emails won't be sent
*/
function validateProductionConfig() {
const strategy = detectEmailStrategy();
if (strategy === 'console') {
console.warn('[VoilaJSX AppKit] Using console email strategy in production. ' +
'Emails will only be logged, not sent. ' +
'Set RESEND_API_KEY or SMTP_HOST for production email sending.');
}
// Validate FROM email is set in production
const fromEmail = process.env.VOILA_EMAIL_FROM_EMAIL;
if (!fromEmail) {
console.warn('[VoilaJSX AppKit] No FROM email configured in production. ' +
'Set VOILA_EMAIL_FROM_EMAIL for professional email sending.');
}
}
/**
* Validates email address format
* @llm-rule WHEN: Validating email addresses in configuration
* @llm-rule AVOID: Complex regex - simple validation for config purposes
*/
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validates numeric environment variable within acceptable range
* @llm-rule WHEN: Validating email configuration numeric values
* @llm-rule AVOID: Using values outside safe ranges - causes timeout or connection issues
*/
function validateNumericEnv(name, min, max) {
const value = process.env[name];
if (!value)
return;
const num = parseInt(value);
if (isNaN(num) || num < min || num > max) {
throw new Error(`Invalid ${name}: "${value}". Must be a number between ${min} and ${max}`);
}
}
/**
* Gets email configuration summary for debugging and health checks
* @llm-rule WHEN: Debugging email configuration or building health check endpoints
* @llm-rule AVOID: Exposing sensitive API keys or passwords - this only shows safe info
*/
export function getConfigSummary() {
const config = getSmartDefaults();
return {
strategy: config.strategy,
fromName: config.from.name,
fromEmail: config.from.email,
resendConfigured: config.strategy === 'resend' && !!config.resend?.apiKey,
smtpConfigured: config.strategy === 'smtp' && !!config.smtp?.host,
environment: config.environment.nodeEnv,
};
}
/**
* Validates that required email configuration is present for production
* @llm-rule WHEN: App startup validation for production deployments
* @llm-rule AVOID: Skipping validation - missing email config causes runtime issues
*/
export function validateProductionRequirements() {
const config = getSmartDefaults();
if (config.environment.isProduction) {
if (config.strategy === 'console') {
console.warn('[VoilaJSX AppKit] Using console email strategy in production. ' +
'Emails will only be logged, not sent. ' +
'Set RESEND_API_KEY or SMTP_HOST for production email sending.');
}
if (config.strategy === 'resend' && !config.resend?.apiKey) {
throw new Error('Resend strategy selected but RESEND_API_KEY not configured. ' +
'Set RESEND_API_KEY environment variable for Resend email sending.');
}
if (config.strategy === 'smtp' && !config.smtp?.host) {
throw new Error('SMTP strategy selected but SMTP_HOST not configured. ' +
'Set SMTP_HOST environment variable for SMTP email sending.');
}
}
}
/**
* Validates startup configuration and throws detailed errors
* @llm-rule WHEN: App startup to ensure email configuration is valid before starting
* @llm-rule AVOID: Skipping validation - catches config issues early
* @llm-rule NOTE: Comprehensive validation for production readiness
*/
export function validateStartupConfiguration() {
const warnings = [];
const errors = [];
try {
const config = getSmartDefaults();
const strategy = config.strategy;
// Strategy-specific validation
switch (strategy) {
case 'resend':
if (!config.resend?.apiKey) {
errors.push('RESEND_API_KEY is required for Resend strategy');
}
else if (!config.resend.apiKey.startsWith('re_')) {
errors.push('RESEND_API_KEY must start with "re_"');
}
break;
case 'smtp':
if (!config.smtp?.host) {
errors.push('SMTP_HOST is required for SMTP strategy');
}
if (config.smtp?.auth.user && !config.smtp?.auth.pass) {
errors.push('SMTP_PASS is required when SMTP_USER is set');
}
if (!config.smtp?.auth.user && config.smtp?.auth.pass) {
errors.push('SMTP_USER is required when SMTP_PASS is set');
}
break;
case 'console':
if (config.environment.isProduction) {
warnings.push('Console strategy in production - emails will only be logged');
}
break;
}
// FROM email validation
if (!config.from.email || config.from.email.includes('example.com')) {
if (config.environment.isProduction) {
errors.push('VOILA_EMAIL_FROM_EMAIL must be set in production');
}
else {
warnings.push('VOILA_EMAIL_FROM_EMAIL not configured - using default');
}
}
// Environment-specific warnings
if (config.environment.isProduction && strategy === 'console') {
warnings.push('No real email provider configured in production');
}
if (config.environment.isDevelopment && strategy !== 'console') {
warnings.push('Using real email provider in development');
}
return {
strategy,
warnings,
errors,
ready: errors.length === 0,
};
}
catch (error) {
errors.push(`Configuration validation failed: ${error.message}`);
return {
strategy: 'unknown',
warnings,
errors,
ready: false,
};
}
}
/**
* Performs comprehensive email system health check
* @llm-rule WHEN: Health check endpoints or monitoring systems
* @llm-rule AVOID: Running in critical path - this is for monitoring only
* @llm-rule NOTE: Returns detailed health status without exposing sensitive data
*/
export function performHealthCheck() {
const issues = [];
let status = 'healthy';
try {
const validation = validateStartupConfiguration();
// Determine overall status
if (validation.errors.length > 0) {
status = 'error';
issues.push(...validation.errors);
}
else if (validation.warnings.length > 0) {
status = 'warning';
issues.push(...validation.warnings);
}
const configured = validation.strategy !== 'console' ||
process.env.NODE_ENV !== 'production';
return {
status,
strategy: validation.strategy,
configured,
issues,
ready: validation.ready,
timestamp: new Date().toISOString(),
};
}
catch (error) {
return {
status: 'error',
strategy: 'unknown',
configured: false,
issues: [`Health check failed: ${error.message}`],
ready: false,
timestamp: new Date().toISOString(),
};
}
}
//# sourceMappingURL=defaults.js.map