Initial commit: Complete NodeJS-native setup
- Migrated from Python pre-commit to NodeJS-native solution - Reorganized documentation structure - Set up Husky + lint-staged for efficient pre-commit hooks - Fixed Dockerfile healthcheck issue - Added comprehensive documentation index
This commit is contained in:
191
services/mailgun.service.ts
Normal file
191
services/mailgun.service.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
// Mailgun Email Service
|
||||
// This service handles email sending via Mailgun API
|
||||
|
||||
import {
|
||||
getMailgunConfig,
|
||||
isMailgunConfigured,
|
||||
isDevelopmentMode,
|
||||
type MailgunConfig,
|
||||
} from './mailgun.config';
|
||||
|
||||
interface EmailTemplate {
|
||||
subject: string;
|
||||
html: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export class MailgunService {
|
||||
private config: MailgunConfig;
|
||||
|
||||
constructor() {
|
||||
this.config = getMailgunConfig();
|
||||
|
||||
// Log configuration status on startup
|
||||
const status = this.getConfigurationStatus();
|
||||
if (status.mode === 'development') {
|
||||
console.log(
|
||||
'📧 Mailgun Service: Running in development mode (emails will be logged only)'
|
||||
);
|
||||
console.log(
|
||||
'💡 To enable real emails, configure Mailgun credentials in .env.local'
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
'📧 Mailgun Service: Configured for production with domain:',
|
||||
status.domain
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getVerificationEmailTemplate(verificationUrl: string): EmailTemplate {
|
||||
return {
|
||||
subject: 'Verify Your Email - Medication Reminder',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #4f46e5;">Verify Your Email Address</h2>
|
||||
<p>Thank you for signing up for Medication Reminder! Please click the button below to verify your email address:</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="${verificationUrl}"
|
||||
style="background-color: #4f46e5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
Verify Email Address
|
||||
</a>
|
||||
</div>
|
||||
<p>Or copy and paste this link into your browser:</p>
|
||||
<p style="word-break: break-all; color: #6b7280;">${verificationUrl}</p>
|
||||
<p style="color: #6b7280; font-size: 14px;">This link will expire in 24 hours.</p>
|
||||
</div>
|
||||
`,
|
||||
text: `
|
||||
Verify Your Email - Medication Reminder
|
||||
|
||||
Thank you for signing up! Please verify your email by visiting:
|
||||
${verificationUrl}
|
||||
|
||||
This link will expire in 24 hours.
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
private getPasswordResetEmailTemplate(resetUrl: string): EmailTemplate {
|
||||
return {
|
||||
subject: 'Reset Your Password - Medication Reminder',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #4f46e5;">Reset Your Password</h2>
|
||||
<p>You requested to reset your password. Click the button below to set a new password:</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="${resetUrl}"
|
||||
style="background-color: #4f46e5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
Reset Password
|
||||
</a>
|
||||
</div>
|
||||
<p>Or copy and paste this link into your browser:</p>
|
||||
<p style="word-break: break-all; color: #6b7280;">${resetUrl}</p>
|
||||
<p style="color: #6b7280; font-size: 14px;">This link will expire in 1 hour. If you didn't request this, please ignore this email.</p>
|
||||
</div>
|
||||
`,
|
||||
text: `
|
||||
Reset Your Password - Medication Reminder
|
||||
|
||||
You requested to reset your password. Visit this link to set a new password:
|
||||
${resetUrl}
|
||||
|
||||
This link will expire in 1 hour. If you didn't request this, please ignore this email.
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
async sendEmail(to: string, template: EmailTemplate): Promise<boolean> {
|
||||
try {
|
||||
// In development mode or when Mailgun is not configured, just log the email
|
||||
if (isDevelopmentMode()) {
|
||||
console.log('📧 Mock Email Sent (Development Mode):', {
|
||||
to,
|
||||
subject: template.subject,
|
||||
from: `${this.config.fromName} <${this.config.fromEmail}>`,
|
||||
html: template.html,
|
||||
text: template.text,
|
||||
note: 'To enable real emails, configure Mailgun credentials in environment variables',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Production Mailgun API call
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
'from',
|
||||
`${this.config.fromName} <${this.config.fromEmail}>`
|
||||
);
|
||||
formData.append('to', to);
|
||||
formData.append('subject', template.subject);
|
||||
formData.append('html', template.html);
|
||||
if (template.text) {
|
||||
formData.append('text', template.text);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${this.config.baseUrl}/${this.config.domain}/messages`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Basic ${btoa(`api:${this.config.apiKey}`)}`,
|
||||
},
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Mailgun API error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('📧 Email sent successfully via Mailgun:', {
|
||||
to,
|
||||
subject: template.subject,
|
||||
messageId: result.id,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Email sending failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async sendVerificationEmail(email: string, token: string): Promise<boolean> {
|
||||
const baseUrl = process.env.APP_BASE_URL || 'http://localhost:5173';
|
||||
const verificationUrl = `${baseUrl}/verify-email?token=${token}`;
|
||||
const template = this.getVerificationEmailTemplate(verificationUrl);
|
||||
return this.sendEmail(email, template);
|
||||
}
|
||||
|
||||
async sendPasswordResetEmail(email: string, token: string): Promise<boolean> {
|
||||
const baseUrl = process.env.APP_BASE_URL || 'http://localhost:5173';
|
||||
const resetUrl = `${baseUrl}/reset-password?token=${token}`;
|
||||
const template = this.getPasswordResetEmailTemplate(resetUrl);
|
||||
return this.sendEmail(email, template);
|
||||
}
|
||||
|
||||
// Utility method to check if Mailgun is properly configured
|
||||
isConfigured(): boolean {
|
||||
return isMailgunConfigured();
|
||||
}
|
||||
|
||||
// Get configuration status for debugging
|
||||
getConfigurationStatus(): {
|
||||
configured: boolean;
|
||||
mode: 'development' | 'production';
|
||||
domain: string;
|
||||
fromEmail: string;
|
||||
} {
|
||||
return {
|
||||
configured: isMailgunConfigured(),
|
||||
mode: isDevelopmentMode() ? 'development' : 'production',
|
||||
domain: this.config.domain,
|
||||
fromEmail: this.config.fromEmail,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const mailgunService = new MailgunService();
|
||||
Reference in New Issue
Block a user