fix: switch contact form to use local SMTP (Maddy) instead of Mailgun
Changed contact form email delivery to use local Maddy SMTP server for better reliability. **Changes:** - Created new SMTP service (lib/smtp.ts) using nodemailer - Configured to use localhost:25 (Maddy SMTP) - Updated contact API to use smtpService instead of mailgunService - Installed nodemailer and @types/nodemailer **Benefits:** - Simpler configuration (no external API dependencies) - Local email delivery (more reliable for internal emails) - No API rate limits or authentication issues - Direct delivery to contact@biblical-guide.com **Roundcube still uses Mailgun SMTP** for outgoing emails from webmail interface. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { mailgunService } from '@/lib/mailgun'
|
import { smtpService } from '@/lib/smtp'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
@@ -46,8 +46,8 @@ export async function POST(request: NextRequest) {
|
|||||||
}, { status: 400 })
|
}, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send email using Mailgun
|
// Send email using local SMTP server (Maddy)
|
||||||
const emailResult = await mailgunService.sendContactForm({
|
const emailResult = await smtpService.sendContactForm({
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
subject,
|
subject,
|
||||||
|
|||||||
111
lib/smtp.ts
Normal file
111
lib/smtp.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import nodemailer from 'nodemailer'
|
||||||
|
|
||||||
|
interface EmailOptions {
|
||||||
|
to: string | string[]
|
||||||
|
subject: string
|
||||||
|
text?: string
|
||||||
|
html?: string
|
||||||
|
from?: string
|
||||||
|
replyTo?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SendEmailResult {
|
||||||
|
success: boolean
|
||||||
|
messageId?: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class SMTPService {
|
||||||
|
private transporter: any
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Configure nodemailer to use local Maddy SMTP server
|
||||||
|
this.transporter = nodemailer.createTransport({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 25,
|
||||||
|
secure: false, // Use STARTTLS
|
||||||
|
tls: {
|
||||||
|
rejectUnauthorized: false // Accept self-signed certificates
|
||||||
|
},
|
||||||
|
// Maddy accepts mail on port 25 without authentication for localhost
|
||||||
|
requireTLS: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendEmail(options: EmailOptions): Promise<SendEmailResult> {
|
||||||
|
try {
|
||||||
|
const mailOptions = {
|
||||||
|
from: options.from || 'Biblical Guide <no-reply@biblical-guide.com>',
|
||||||
|
to: Array.isArray(options.to) ? options.to.join(', ') : options.to,
|
||||||
|
subject: options.subject,
|
||||||
|
text: options.text,
|
||||||
|
html: options.html,
|
||||||
|
replyTo: options.replyTo
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = await this.transporter.sendMail(mailOptions)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
messageId: info.messageId
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('SMTP 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 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: 'contact@biblical-guide.com',
|
||||||
|
subject: `Contact Form: ${data.subject}`,
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
replyTo: data.email
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async testConnection(): Promise<{ success: boolean; error?: string }> {
|
||||||
|
try {
|
||||||
|
await this.transporter.verify()
|
||||||
|
return { success: true }
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Connection failed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const smtpService = new SMTPService()
|
||||||
1405
package-lock.json
generated
1405
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,7 @@
|
|||||||
"negotiator": "^1.0.0",
|
"negotiator": "^1.0.0",
|
||||||
"next": "^15.5.3",
|
"next": "^15.5.3",
|
||||||
"next-intl": "^4.3.9",
|
"next-intl": "^4.3.9",
|
||||||
|
"nodemailer": "^7.0.9",
|
||||||
"openai": "^5.22.0",
|
"openai": "^5.22.0",
|
||||||
"pdf-parse": "^1.1.1",
|
"pdf-parse": "^1.1.1",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
|
"@types/nodemailer": "^7.0.2",
|
||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
"tsx": "^4.20.5"
|
"tsx": "^4.20.5"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user