Add Mailgun admin tools and contact API
This commit is contained in:
228
lib/mailgun.ts
Normal file
228
lib/mailgun.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import Mailgun from 'mailgun.js'
|
||||
import FormData from 'form-data'
|
||||
import { prisma } from './db'
|
||||
|
||||
interface EmailOptions {
|
||||
to: string | string[]
|
||||
subject: string
|
||||
text?: string
|
||||
html?: string
|
||||
from?: string
|
||||
replyTo?: string
|
||||
}
|
||||
|
||||
interface SendEmailResult {
|
||||
success: boolean
|
||||
messageId?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
class MailgunService {
|
||||
private mailgun: any
|
||||
private settings: any = null
|
||||
|
||||
constructor() {
|
||||
this.mailgun = new Mailgun(FormData)
|
||||
}
|
||||
|
||||
private async getSettings() {
|
||||
if (!this.settings) {
|
||||
this.settings = await prisma.mailgunSettings.findFirst({
|
||||
where: { isEnabled: true },
|
||||
orderBy: { updatedAt: 'desc' }
|
||||
})
|
||||
}
|
||||
return this.settings
|
||||
}
|
||||
|
||||
private getMg() {
|
||||
const settings = this.settings
|
||||
if (!settings) {
|
||||
throw new Error('No Mailgun settings found')
|
||||
}
|
||||
|
||||
return this.mailgun.client({
|
||||
username: 'api',
|
||||
key: settings.apiKey,
|
||||
url: settings.region === 'EU' ? 'https://api.eu.mailgun.net' : 'https://api.mailgun.net'
|
||||
})
|
||||
}
|
||||
|
||||
async sendEmail(options: EmailOptions): Promise<SendEmailResult> {
|
||||
try {
|
||||
const settings = await this.getSettings()
|
||||
|
||||
if (!settings) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Mailgun not configured'
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.isEnabled) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Mailgun is disabled'
|
||||
}
|
||||
}
|
||||
|
||||
const mg = this.getMg()
|
||||
|
||||
const emailData = {
|
||||
from: options.from || `${settings.fromName} <${settings.fromEmail}>`,
|
||||
to: Array.isArray(options.to) ? options.to.join(', ') : options.to,
|
||||
subject: options.subject,
|
||||
text: options.text,
|
||||
html: options.html,
|
||||
'h:Reply-To': options.replyTo || settings.replyToEmail
|
||||
}
|
||||
|
||||
// Remove undefined values
|
||||
Object.keys(emailData).forEach(key => {
|
||||
if (emailData[key as keyof typeof emailData] === undefined) {
|
||||
delete emailData[key as keyof typeof emailData]
|
||||
}
|
||||
})
|
||||
|
||||
// In test mode, use Mailgun's test domain
|
||||
if (settings.testMode) {
|
||||
console.log('Mailgun test mode - email would be sent:', emailData)
|
||||
return {
|
||||
success: true,
|
||||
messageId: `test-${Date.now()}`
|
||||
}
|
||||
}
|
||||
|
||||
const response = await mg.messages.create(settings.domain, emailData)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messageId: response.id
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Mailgun send error:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sendContactForm(data: {
|
||||
name: string
|
||||
email: string
|
||||
subject: string
|
||||
message: string
|
||||
}): Promise<SendEmailResult> {
|
||||
const settings = await this.getSettings()
|
||||
|
||||
if (!settings) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Mailgun not configured'
|
||||
}
|
||||
}
|
||||
|
||||
const html = `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${data.name}</p>
|
||||
<p><strong>Email:</strong> ${data.email}</p>
|
||||
<p><strong>Subject:</strong> ${data.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<div style="background: #f5f5f5; padding: 15px; border-radius: 5px;">
|
||||
${data.message.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
`
|
||||
|
||||
const text = `
|
||||
New Contact Form Submission
|
||||
|
||||
Name: ${data.name}
|
||||
Email: ${data.email}
|
||||
Subject: ${data.subject}
|
||||
|
||||
Message:
|
||||
${data.message}
|
||||
`
|
||||
|
||||
return this.sendEmail({
|
||||
to: settings.fromEmail, // Send to site admin
|
||||
subject: `Contact Form: ${data.subject}`,
|
||||
html,
|
||||
text,
|
||||
replyTo: data.email
|
||||
})
|
||||
}
|
||||
|
||||
async sendPasswordReset(email: string, resetToken: string): Promise<SendEmailResult> {
|
||||
const settings = await this.getSettings()
|
||||
|
||||
if (!settings) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Mailgun not configured'
|
||||
}
|
||||
}
|
||||
|
||||
// This would need to be updated with your actual domain
|
||||
const resetUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/reset-password?token=${resetToken}`
|
||||
|
||||
const html = `
|
||||
<h2>Password Reset Request</h2>
|
||||
<p>You requested to reset your password for your Biblical Guide account.</p>
|
||||
<p>Click the link below to reset your password:</p>
|
||||
<p><a href="${resetUrl}" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Reset Password</a></p>
|
||||
<p>This link will expire in 1 hour.</p>
|
||||
<p>If you didn't request this, please ignore this email.</p>
|
||||
`
|
||||
|
||||
const text = `
|
||||
Password Reset Request
|
||||
|
||||
You requested to reset your password for your Biblical Guide account.
|
||||
|
||||
Click the link below to reset your password:
|
||||
${resetUrl}
|
||||
|
||||
This link will expire in 1 hour.
|
||||
|
||||
If you didn't request this, please ignore this email.
|
||||
`
|
||||
|
||||
return this.sendEmail({
|
||||
to: email,
|
||||
subject: 'Reset Your Password - Biblical Guide',
|
||||
html,
|
||||
text
|
||||
})
|
||||
}
|
||||
|
||||
async testConnection(): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const settings = await this.getSettings()
|
||||
|
||||
if (!settings) {
|
||||
return { success: false, error: 'No settings found' }
|
||||
}
|
||||
|
||||
const mg = this.getMg()
|
||||
|
||||
// Test by getting domain info
|
||||
await mg.domains.get(settings.domain)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Connection failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cached settings when they are updated
|
||||
clearCache() {
|
||||
this.settings = null
|
||||
}
|
||||
}
|
||||
|
||||
export const mailgunService = new MailgunService()
|
||||
Reference in New Issue
Block a user