first commit

This commit is contained in:
AyrisAI
2026-05-14 01:57:52 +03:00
parent 863a32cd35
commit 4a9196f483
47 changed files with 12043 additions and 102 deletions

131
lib/smtp.ts Normal file
View File

@@ -0,0 +1,131 @@
/**
* 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();
}
}