waitlist-mailer
Version:
Modern, modular TypeScript library for managing waitlists with pluggable storage and mail providers. Supports MongoDB, SQL databases, and custom adapters with zero required dependencies for basic usage.
155 lines (141 loc) • 4.6 kB
text/typescript
/**
* Nodemailer-based mail provider for waitlist-mailer.
* Sends emails through SMTP servers using Nodemailer.
* Requires: nodemailer (already a core dependency)
*/
import { MailProvider, EmailContext } from '../../types';
import nodemailer, { Transporter, SendMailOptions } from 'nodemailer';
/**
* Nodemailer SMTP configuration.
*/
export interface NodemailerConfig {
host: string;
port: number;
user: string;
pass: string;
secure?: boolean;
from?: string;
name?: string;
}
/**
* Nodemailer-based mail provider implementation.
* Sends confirmation emails via SMTP.
*/
export class NodemailerProvider implements MailProvider {
private transporter: Transporter;
private fromEmail: string;
private fromName: string;
/**
* Create a new NodemailerProvider instance.
* @param config - SMTP configuration
* @throws {Error} If nodemailer is not installed
*/
constructor(private config: NodemailerConfig) {
// Verify nodemailer is available
try {
if (!nodemailer || !nodemailer.createTransport) {
throw new Error('nodemailer is not properly initialized');
}
} catch (error) {
throw new Error(
'NodemailerProvider requires nodemailer to be installed. ' +
'Install it with: npm install nodemailer'
);
}
this.fromEmail = config.from || config.user;
this.fromName = config.name || 'Waitlist Manager';
this.transporter = nodemailer.createTransport({
host: config.host,
port: config.port,
secure: config.secure ?? config.port === 465,
auth: {
user: config.user,
pass: config.pass,
},
});
}
/**
* Verify the transporter configuration.
* @returns true if configuration is valid
*/
async verify(): Promise<boolean> {
try {
await this.transporter.verify();
return true;
} catch (error) {
console.error('Nodemailer verification failed:', error);
return false;
}
}
/**
* Send a confirmation email.
* @param email - Recipient email address
* @param context - Template context with variables
* @returns true if the email was sent successfully
*/
async sendConfirmation(email: string, context: EmailContext): Promise<boolean> {
try {
const subject = this.buildSubject(context);
const html = this.buildHtml(context);
const mailOptions: SendMailOptions = {
from: `"${this.fromName}" <${this.fromEmail}>`,
to: email,
subject,
html,
};
await this.transporter.sendMail(mailOptions);
return true;
} catch (error) {
console.error(`Failed to send email to ${email}:`, error);
return false;
}
}
/**
* Build the email subject.
* Allows customization through context.
*/
private buildSubject(context: EmailContext): string {
return context.subject || `Welcome to ${context.companyName || 'our platform'}`;
}
/**
* Build the HTML body.
* Provides a default template if customHtml is not provided.
*/
private buildHtml(context: EmailContext): string {
if (context.customHtml) {
return context.customHtml;
}
// Default HTML template
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #007bff; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; }
.footer { text-align: center; padding: 20px; color: #666; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Welcome to ${context.companyName || 'Our Platform'}</h1>
</div>
<div class="content">
<p>Hello,</p>
<p>Thank you for joining our waitlist! We're excited to have you on board.</p>
<p>You'll be among the first to know when we launch.</p>
${context.customUrl ? `<p><a href="${context.customUrl}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; display: inline-block;">Learn More</a></p>` : ''}
</div>
<div class="footer">
<p>© 2024 ${context.companyName || 'Our Platform'}. All rights reserved.</p>
</div>
</div>
</body>
</html>
`;
}
}