@voilajsx/appkit
Version:
Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development
251 lines • 8.97 kB
JavaScript
/**
* Ultra-simple email sending that just works with automatic provider detection
* @module @voilajsx/appkit/email
* @file src/email/index.ts
*
* @llm-rule WHEN: Building apps that need email sending with zero configuration
* @llm-rule AVOID: Complex email setups - this auto-detects Resend/SMTP/Console from environment
* @llm-rule NOTE: Uses emailClass.get() pattern like auth - get() → email.send() → done
* @llm-rule NOTE: Common pattern - emailClass.get() → email.send({ to, subject, text }) → sent
*/
import { EmailClass } from './email.js';
import { getSmartDefaults, validateProductionRequirements, validateStartupConfiguration, performHealthCheck } from './defaults.js';
// Global email instance for performance (like auth module)
let globalEmail = null;
/**
* Get email instance - the only function you need to learn
* Strategy auto-detected from environment (RESEND_API_KEY → Resend, SMTP_HOST → SMTP, default → Console)
* @llm-rule WHEN: Need email sending in any part of your app - this is your main entry point
* @llm-rule AVOID: Creating EmailClass directly - always use this function
* @llm-rule NOTE: Typical flow - get() → email.send() → email delivered/logged
*/
function get() {
// Lazy initialization - parse environment once (like auth)
if (!globalEmail) {
const config = getSmartDefaults();
globalEmail = new EmailClass(config);
}
return globalEmail;
}
/**
* Clear email instance and disconnect - essential for testing
* @llm-rule WHEN: Testing email logic with different configurations or app shutdown
* @llm-rule AVOID: Using in production except for graceful shutdown
*/
async function clear() {
if (globalEmail) {
await globalEmail.disconnect();
globalEmail = null;
}
}
/**
* Reset email configuration (useful for testing)
* @llm-rule WHEN: Testing email logic with different environment configurations
* @llm-rule AVOID: Using in production - only for tests and development
*/
async function reset(newConfig) {
// Clear existing instance
await clear();
// Reset configuration
if (newConfig) {
const defaults = getSmartDefaults();
const config = { ...defaults, ...newConfig };
globalEmail = new EmailClass(config);
}
else {
globalEmail = null; // Will reload from environment on next get()
}
}
/**
* Get active email strategy for debugging
* @llm-rule WHEN: Debugging or health checks to see which strategy is active (Resend/SMTP/Console)
* @llm-rule AVOID: Using for application logic - email should be transparent
*/
function getStrategy() {
const email = get();
return email.getStrategy();
}
/**
* Get email configuration summary for debugging
* @llm-rule WHEN: Health checks or debugging email configuration
* @llm-rule AVOID: Exposing sensitive API keys or passwords - this only shows safe info
*/
function getConfig() {
const email = get();
return email.getConfig();
}
/**
* Check if Resend is available and configured
* @llm-rule WHEN: Conditional logic based on email capabilities
* @llm-rule AVOID: Complex email detection - just use email normally, it handles strategy
*/
function hasResend() {
return !!process.env.RESEND_API_KEY;
}
/**
* Check if SMTP is available and configured
* @llm-rule WHEN: Conditional logic based on email capabilities
* @llm-rule AVOID: Complex email detection - just use email normally, it handles strategy
*/
function hasSmtp() {
return !!process.env.SMTP_HOST;
}
/**
* Check if any email provider is configured (not just console)
* @llm-rule WHEN: Determining if real emails can be sent
* @llm-rule AVOID: Using for validation - email.send() will return success/error appropriately
*/
function hasProvider() {
return hasResend() || hasSmtp();
}
/**
* Send simple email (convenience function)
* @llm-rule WHEN: Quick email sending without getting instance first
* @llm-rule AVOID: For complex emails - use get() and full EmailData object instead
*/
async function send(data) {
const email = get();
return await email.send(data);
}
/**
* Send simple text email (ultra-convenience function)
* @llm-rule WHEN: Sending basic notifications or alerts quickly
* @llm-rule AVOID: For formatted emails - use send() with HTML content instead
*/
async function sendText(to, subject, text) {
const email = get();
return await email.sendText(to, subject, text);
}
/**
* Validate email configuration at startup with detailed feedback
* @llm-rule WHEN: App startup to ensure email is properly configured
* @llm-rule AVOID: Skipping validation - missing email config causes runtime issues
* @llm-rule NOTE: Returns validation results instead of throwing - allows graceful handling
*/
function validateConfig() {
try {
const validation = validateStartupConfiguration();
if (validation.errors.length > 0) {
console.error('[VoilaJSX AppKit] Email configuration errors:', validation.errors);
}
if (validation.warnings.length > 0) {
console.warn('[VoilaJSX AppKit] Email configuration warnings:', validation.warnings);
}
if (validation.ready) {
console.log(`✅ [VoilaJSX AppKit] Email configured with ${validation.strategy} strategy`);
}
return {
valid: validation.errors.length === 0,
strategy: validation.strategy,
warnings: validation.warnings,
errors: validation.errors,
ready: validation.ready,
};
}
catch (error) {
const errorMessage = error.message;
console.error('[VoilaJSX AppKit] Email configuration validation failed:', errorMessage);
return {
valid: false,
strategy: 'unknown',
warnings: [],
errors: [errorMessage],
ready: false,
};
}
}
/**
* Validate production requirements and throw if critical issues found
* @llm-rule WHEN: Production deployment validation - ensures email works in production
* @llm-rule AVOID: Skipping in production - email failures are often silent
* @llm-rule NOTE: Throws on critical issues, warns on non-critical ones
*/
function validateProduction() {
try {
validateProductionRequirements();
if (process.env.NODE_ENV === 'production' && !hasProvider()) {
console.warn('[VoilaJSX AppKit] No email provider configured in production. ' +
'Set RESEND_API_KEY or SMTP_HOST to send real emails.');
}
console.log('✅ [VoilaJSX AppKit] Production email requirements validated');
}
catch (error) {
console.error('[VoilaJSX AppKit] Production email validation failed:', error.message);
throw error;
}
}
/**
* Get comprehensive health check status for monitoring
* @llm-rule WHEN: Health check endpoints or monitoring systems
* @llm-rule AVOID: Using in critical application path - this is for monitoring only
* @llm-rule NOTE: Returns detailed status without exposing sensitive configuration
*/
function getHealthStatus() {
return performHealthCheck();
}
/**
* Graceful shutdown for email instance
* @llm-rule WHEN: App shutdown or process termination
* @llm-rule AVOID: Abrupt process exit - graceful shutdown prevents connection issues
*/
async function shutdown() {
console.log('🔄 [AppKit] Email graceful shutdown...');
try {
await clear();
console.log('✅ [AppKit] Email shutdown complete');
}
catch (error) {
console.error('❌ [AppKit] Email shutdown error:', error.message);
}
}
/**
* Single email export with minimal API (like auth module)
*/
export const emailClass = {
// Core method (like auth.get())
get,
// Utility methods
clear,
reset,
getStrategy,
getConfig,
hasResend,
hasSmtp,
hasProvider,
// Convenience methods
send,
sendText,
// Validation and lifecycle
validateConfig,
validateProduction,
getHealthStatus,
shutdown,
};
export { EmailClass } from './email.js';
// Default export
export default emailClass;
// Auto-setup graceful shutdown handlers
if (typeof process !== 'undefined') {
// Handle graceful shutdown
const shutdownHandler = () => {
shutdown().finally(() => {
process.exit(0);
});
};
process.on('SIGTERM', shutdownHandler);
process.on('SIGINT', shutdownHandler);
// Handle uncaught errors
process.on('uncaughtException', (error) => {
console.error('[AppKit] Uncaught exception during email operation:', error);
shutdown().finally(() => {
process.exit(1);
});
});
process.on('unhandledRejection', (reason) => {
console.error('[AppKit] Unhandled rejection during email operation:', reason);
shutdown().finally(() => {
process.exit(1);
});
});
}
//# sourceMappingURL=index.js.map