import nodemailer from "nodemailer" import { FastifyInstance } from "fastify" import { eq } from "drizzle-orm" import { sendMailAsUser } from "../utils/emailengine" import { encrypt, decrypt } from "../utils/crypt" import { userCredentials } from "../../db/schema" // Pfad ggf. anpassen // @ts-ignore import MailComposer from "nodemailer/lib/mail-composer/index.js" import { ImapFlow } from "imapflow" export default async function emailAsUserRoutes(server: FastifyInstance) { // ====================================================================== // CREATE OR UPDATE EMAIL ACCOUNT // ====================================================================== server.post("/email/accounts/:id?", async (req, reply) => { try { if (!req.user?.tenant_id) { return reply.code(400).send({ error: "No tenant selected" }) } const { id } = req.params as { id?: string } const body = req.body as { email: string password: string smtp_host: string smtp_port: number smtp_ssl: boolean imap_host: string imap_port: number imap_ssl: boolean } // ----------------------------- // UPDATE EXISTING // ----------------------------- if (id) { const saveData = { emailEncrypted: body.email ? encrypt(body.email) : undefined, passwordEncrypted: body.password ? encrypt(body.password) : undefined, smtpHostEncrypted: body.smtp_host ? encrypt(body.smtp_host) : undefined, smtpPort: body.smtp_port, smtpSsl: body.smtp_ssl, imapHostEncrypted: body.imap_host ? encrypt(body.imap_host) : undefined, imapPort: body.imap_port, imapSsl: body.imap_ssl, } await server.db .update(userCredentials) //@ts-ignore .set(saveData) .where(eq(userCredentials.id, id)) return reply.send({ success: true }) } // ----------------------------- // CREATE NEW // ----------------------------- const insertData = { userId: req.user.user_id, tenantId: req.user.tenant_id, type: "mail", emailEncrypted: encrypt(body.email), passwordEncrypted: encrypt(body.password), smtpHostEncrypted: encrypt(body.smtp_host), smtpPort: body.smtp_port, smtpSsl: body.smtp_ssl, imapHostEncrypted: encrypt(body.imap_host), imapPort: body.imap_port, imapSsl: body.imap_ssl, } //@ts-ignore await server.db.insert(userCredentials).values(insertData) return reply.send({ success: true }) } catch (err) { console.error("POST /email/accounts error:", err) return reply.code(500).send({ error: "Internal Server Error" }) } }) // ====================================================================== // GET SINGLE OR ALL ACCOUNTS // ====================================================================== server.get("/email/accounts/:id?", async (req, reply) => { try { if (!req.user?.tenant_id) { return reply.code(400).send({ error: "No tenant selected" }) } const { id } = req.params as { id?: string } // ============================================================ // LOAD SINGLE ACCOUNT // ============================================================ if (id) { const rows = await server.db .select() .from(userCredentials) .where(eq(userCredentials.id, id)) const row = rows[0] if (!row) return reply.code(404).send({ error: "Not found" }) const returnData: any = {} Object.entries(row).forEach(([key, val]) => { if (key.endsWith("Encrypted")) { const cleanKey = key.replace("Encrypted", "") // @ts-ignore returnData[cleanKey] = decrypt(val as string) } else { returnData[key] = val } }) return reply.send(returnData) } // ============================================================ // LOAD ALL ACCOUNTS FOR TENANT // ============================================================ const rows = await server.db .select() .from(userCredentials) .where(eq(userCredentials.tenantId, req.user.tenant_id)) const accounts = rows.map(row => { const temp: any = {} Object.entries(row).forEach(([key, val]) => { if (key.endsWith("Encrypted")) { // @ts-ignore temp[key.replace("Encrypted", "")] = decrypt(val as string) } else { temp[key] = val } }) return temp }) return reply.send(accounts) } catch (err) { console.error("GET /email/accounts error:", err) return reply.code(500).send({ error: "Internal Server Error" }) } }) // ====================================================================== // SEND EMAIL + SAVE IN IMAP SENT FOLDER // ====================================================================== server.post("/email/send", async (req, reply) => { try { const body = req.body as { to: string cc?: string bcc?: string subject?: string text?: string html?: string attachments?: any account: string } // Fetch email credentials const rows = await server.db .select() .from(userCredentials) .where(eq(userCredentials.id, body.account)) const row = rows[0] if (!row) return reply.code(404).send({ error: "Account not found" }) const accountData: any = {} Object.entries(row).forEach(([key, val]) => { if (key.endsWith("Encrypted")) { // @ts-ignore accountData[key.replace("Encrypted", "")] = decrypt(val as string) } else { accountData[key] = val } }) // ------------------------- // SEND EMAIL VIA SMTP // ------------------------- const transporter = nodemailer.createTransport({ host: accountData.smtpHost, port: accountData.smtpPort, secure: accountData.smtpSsl, auth: { user: accountData.email, pass: accountData.password, }, }) const message = { from: accountData.email, to: body.to, cc: body.cc, bcc: body.bcc, subject: body.subject, html: body.html, text: body.text, attachments: body.attachments, } const info = await transporter.sendMail(message) // ------------------------- // SAVE TO IMAP SENT FOLDER // ------------------------- const imap = new ImapFlow({ host: accountData.imapHost, port: accountData.imapPort, secure: accountData.imapSsl, auth: { user: accountData.email, pass: accountData.password, }, }) await imap.connect() const mail = new MailComposer(message) const raw = await mail.compile().build() for await (const mailbox of await imap.list()) { if (mailbox.specialUse === "\\Sent") { await imap.mailboxOpen(mailbox.path) await imap.append(mailbox.path, raw, ["\\Seen"]) await imap.logout() } } return reply.send({ success: true }) } catch (err) { console.error("POST /email/send error:", err) return reply.code(500).send({ error: "Failed to send email" }) } }) }