import nodemailer from 'nodemailer';
import { getCredentialsFromSession, decryptCredentials } from '~/server/utils/encryption';
import { uploadFile, getMinioClient } from '~/server/utils/minio';
import { updateInterest } from '~/server/utils/nocodb';
export default defineEventHandler(async (event) => {
const xTagHeader = getRequestHeader(event, "x-tag");
console.log('[Email Send] Request received with x-tag:', xTagHeader);
if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) {
console.error('[Email Send] Authentication failed - invalid x-tag');
throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
}
try {
const body = await readBody(event);
const {
to,
subject,
body: emailBody,
interestId,
sessionId,
includeSignature = true,
signatureConfig,
attachments = []
} = body;
console.log('[Email Send] Request body:', {
to,
subject,
hasBody: !!emailBody,
interestId,
hasSessionId: !!sessionId,
attachmentCount: attachments.length
});
if (!to || !subject || !emailBody || !sessionId) {
console.error('[Email Send] Missing required fields:', {
hasTo: !!to,
hasSubject: !!subject,
hasBody: !!emailBody,
hasSessionId: !!sessionId
});
throw createError({
statusCode: 400,
statusMessage: "To, subject, body, and sessionId are required"
});
}
// Get encrypted credentials from session
console.log('[Email Send] Getting credentials for sessionId:', sessionId);
const encryptedCredentials = getCredentialsFromSession(sessionId);
if (!encryptedCredentials) {
console.error('[Email Send] No credentials found for sessionId:', sessionId);
throw createError({
statusCode: 401,
statusMessage: "Email credentials not found. Please reconnect."
});
}
// Decrypt credentials
console.log('[Email Send] Decrypting credentials');
let email: string;
let password: string;
try {
const decrypted = decryptCredentials(encryptedCredentials);
email = decrypted.email;
password = decrypted.password;
console.log('[Email Send] Successfully decrypted credentials for:', email);
} catch (decryptError) {
console.error('[Email Send] Failed to decrypt credentials:', decryptError);
throw createError({
statusCode: 401,
statusMessage: "Failed to decrypt email credentials. Please reconnect."
});
}
// Get user info for signature
const defaultName = email.split('@')[0].replace('.', ' ').replace(/\b\w/g, l => l.toUpperCase());
// Build email signature with customizable fields
const sig = signatureConfig || {};
const contactLines = sig.contactInfo ? sig.contactInfo.split('\n').filter((line: string) => line.trim()).join('
') : '';
const signature = includeSignature ? `
${sig.name || defaultName}
${sig.title || 'Sales & Marketing Director'}
${sig.company || 'Port Nimara'}
${contactLines ? contactLines + '
' : ''}
${sig.email || email}
The information in this message is confidential and may be privileged.
It is intended for the addressee alone.
If you are not the intended recipient it is prohibited to disclose, use or copy this information.
Please contact the Sender immediately should this message have been transmitted incorrectly.
` : '';
// Convert plain text body to HTML with line breaks
const htmlBody = emailBody.replace(/\n/g, '
') + signature;
// Configure SMTP transport
const transporter = nodemailer.createTransport({
host: process.env.NUXT_EMAIL_SMTP_HOST || 'mail.portnimara.com',
port: parseInt(process.env.NUXT_EMAIL_SMTP_PORT || '587'),
secure: false, // false for STARTTLS
auth: {
user: email,
pass: password
},
tls: {
rejectUnauthorized: false // Allow self-signed certificates
}
});
// Prepare email attachments
console.log('[Email Send] Processing', attachments.length, 'attachments');
const emailAttachments = [];
if (attachments && attachments.length > 0) {
const client = getMinioClient();
for (const attachment of attachments) {
try {
// Determine which bucket to use
const bucket = attachment.bucket || 'client-portal'; // Default to client-portal
console.log('[Email Send] Processing attachment:', attachment.name, 'from bucket:', bucket, 'path:', attachment.path);
// Download file from MinIO
const stream = await client.getObject(bucket, attachment.path);
const chunks: Buffer[] = [];
await new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', resolve);
stream.on('error', reject);
});
const content = Buffer.concat(chunks);
console.log('[Email Send] Successfully downloaded attachment:', attachment.name, 'size:', content.length);
emailAttachments.push({
filename: attachment.name,
content: content
});
} catch (error) {
console.error(`[Email Send] Failed to attach file ${attachment.name} from bucket ${attachment.bucket}:`, error);
// Continue with other attachments
}
}
}
// Send email
const fromName = sig.name || defaultName;
console.log('[Email Send] Sending email from:', email, 'to:', to);
const info = await transporter.sendMail({
from: `"${fromName}" <${email}>`,
to: to,
subject: subject,
text: emailBody, // Plain text version
html: htmlBody, // HTML version with signature
attachments: emailAttachments
});
console.log('[Email Send] Email sent successfully, messageId:', info.messageId);
// Store email in MinIO for thread history and update EOI Time Sent
if (interestId) {
try {
const emailData = {
id: info.messageId,
from: email,
to: to,
subject: subject,
body: emailBody,
html: htmlBody,
timestamp: new Date().toISOString(),
direction: 'sent',
interestId: interestId,
attachments: attachments // Include attachment info
};
const objectName = `interest-${interestId}/${Date.now()}-sent.json`;
const buffer = Buffer.from(JSON.stringify(emailData, null, 2));
// Upload to the client-emails bucket
const { getMinioClient } = await import('~/server/utils/minio');
const client = getMinioClient();
await client.putObject('client-emails', objectName, buffer, buffer.length, {
'Content-Type': 'application/json',
});
// Update EOI Time Sent if the email contains an EOI link
if (emailBody.includes('signatures.portnimara.dev/sign/')) {
try {
await updateInterest(interestId, {
'EOI Time Sent': new Date().toISOString()
});
} catch (updateError) {
console.error('Failed to update EOI Time Sent:', updateError);
// Continue even if update fails
}
}
} catch (storageError) {
console.error('Failed to store email in MinIO:', storageError);
// Continue even if storage fails
}
}
return {
success: true,
message: "Email sent successfully",
messageId: info.messageId
};
} catch (error) {
console.error('[Email Send] Failed to send email:', error);
console.error('[Email Send] Error stack:', error instanceof Error ? error.stack : 'No stack trace');
if (error instanceof Error) {
throw createError({
statusCode: 500,
statusMessage: `Failed to send email: ${error.message}`
});
} else {
throw createError({
statusCode: 500,
statusMessage: "An unexpected error occurred",
});
}
}
});