Files
webmailserver/lib/smtp.ts
2026-05-14 01:57:52 +03:00

132 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* lib/smtp.ts
* SMTP mail sending via nodemailer + IMAP Sent folder append.
* Uses user's mailbox credentials for authenticated SMTP.
*/
import nodemailer from "nodemailer";
import { ImapFlow } from "imapflow";
import type { MailCredentials } from "./imap";
const SMTP_HOST = process.env.MAILCOW_API_URL
? new URL(process.env.MAILCOW_API_URL).hostname
: "localhost";
const SMTP_PORT = parseInt(process.env.SMTP_PORT ?? "587");
const IMAP_HOST = SMTP_HOST;
const IMAP_PORT = parseInt(process.env.IMAP_PORT ?? "993");
export interface SendMailOptions {
to: string;
cc?: string;
subject: string;
html: string;
text?: string;
inReplyTo?: string;
references?: string;
attachments?: {
filename: string;
content: Buffer | string;
contentType?: string;
}[];
}
export async function sendMail(
creds: MailCredentials,
options: SendMailOptions
): Promise<{ success: boolean; messageId?: string; error?: string }> {
try {
const transporter = nodemailer.createTransport({
host: SMTP_HOST,
port: SMTP_PORT,
secure: SMTP_PORT === 465,
auth: {
user: creds.email,
pass: creds.password,
},
tls: {
// Mailcow self-signed cert'e izin ver
rejectUnauthorized: false,
},
});
const mailOptions = {
from: creds.email,
to: options.to,
cc: options.cc || undefined,
subject: options.subject,
html: options.html,
text: options.text || undefined,
inReplyTo: options.inReplyTo || undefined,
references: options.references || undefined,
attachments: options.attachments?.map((a) => ({
filename: a.filename,
content: a.content,
contentType: a.contentType,
})),
};
const result = await transporter.sendMail(mailOptions);
// Gönderilen maili Sent klasörüne kaydet (IMAP APPEND)
try {
await appendToSent(creds, mailOptions);
} catch (e) {
// Sent'e kaydetme başarısız olursa mail yine de gitmiş olur
console.error("Sent klasörüne kaydetme hatası:", e);
}
return { success: true, messageId: result.messageId };
} catch (err: any) {
return { success: false, error: err?.message ?? "SMTP hatası" };
}
}
/**
* Gönderilen maili IMAP Sent klasörüne kaydet.
* Mailcow'da Sent klasörü "Sent" olarak adlandırılır.
*/
async function appendToSent(
creds: MailCredentials,
mailOptions: Record<string, any>
): Promise<void> {
// nodemailer ile raw mesaj oluştur
const transporter = nodemailer.createTransport({ jsonTransport: true });
const compiled = await transporter.sendMail(mailOptions);
const rawMessage = JSON.parse(compiled.message);
// Gerçek raw RFC822 mesajı oluştur
const buildTransporter = nodemailer.createTransport({ streamTransport: true });
const built = await buildTransporter.sendMail(mailOptions);
const chunks: Buffer[] = [];
for await (const chunk of built.message as any) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
const rawBuffer = Buffer.concat(chunks);
const client = new ImapFlow({
host: IMAP_HOST,
port: IMAP_PORT,
secure: true,
auth: { user: creds.email, pass: creds.password },
logger: false,
});
await client.connect();
try {
// Sent klasörünü bul
const mailboxes = await client.list();
let sentPath = "Sent";
for (const mb of mailboxes) {
if (mb.specialUse === "\\Sent") {
sentPath = mb.path;
break;
}
}
// Mesajı Sent klasörüne ekle (Seen flag ile)
await client.append(sentPath, rawBuffer, ["\\Seen"]);
} finally {
await client.logout();
}
}