Files
FEDEO/backend/src/routes/helpdesk.inbound.email.ts

106 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// modules/helpdesk/helpdesk.inbound.email.ts
import { FastifyPluginAsync } from 'fastify'
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
import { findCustomerOrContactByEmailOrDomain } from "../utils/helpers";
import { eq } from "drizzle-orm";
import { helpdesk_conversations, helpdesk_messages } from "../../db/schema";
// -------------------------------------------------------------
// 📧 Interne M2M-Route für eingehende E-Mails
// -------------------------------------------------------------
const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
server.post('/helpdesk/inbound-email', async (req, res) => {
const {
tenant_id,
channel_id,
from,
subject,
text,
message_id,
in_reply_to,
} = req.body as {
tenant_id: number
channel_id: string
from: {address: string, name: string}
subject: string
text: string
message_id: string
in_reply_to: string
}
if (!tenant_id || !from?.address || !text) {
return res.status(400).send({ error: 'Invalid payload' })
}
server.log.info(`[InboundEmail] Neue Mail von ${from.address} für Tenant ${tenant_id}`)
// 1⃣ Kunde & Kontakt ermitteln
const { customer, contact: contactPerson } =
(await findCustomerOrContactByEmailOrDomain(server, from.address, tenant_id)) || {}
// 2⃣ Kontakt anlegen oder laden
const contact = await getOrCreateContact(server, tenant_id, {
email: from.address,
display_name: from.name || from.address,
customer_id: customer,
contact_id: contactPerson,
})
// 3⃣ Konversation anhand In-Reply-To suchen
let conversationId: string | null = null
if (in_reply_to) {
const msg = await server.db
.select({ conversationId: helpdesk_messages.conversationId })
.from(helpdesk_messages)
.where(eq(helpdesk_messages.externalMessageId, in_reply_to))
.limit(1)
conversationId = msg[0]?.conversationId || null
}
// 4⃣ Neue Konversation anlegen falls keine existiert
let conversation
if (!conversationId) {
conversation = await createConversation(server, {
tenant_id,
contact,
channel_instance_id: channel_id,
subject: subject || '(kein Betreff)',
customer_id: customer,
contact_person_id: contactPerson,
})
conversationId = conversation.id
} else {
const rows = await server.db
.select()
.from(helpdesk_conversations)
.where(eq(helpdesk_conversations.id, conversationId))
.limit(1)
conversation = rows[0]
}
// 5⃣ Nachricht speichern
await addMessage(server, {
tenant_id,
conversation_id: conversationId,
direction: 'incoming',
payload: { type: 'text', text },
external_message_id: message_id,
raw_meta: { source: 'email' },
})
server.log.info(`[InboundEmail] Ticket ${conversationId} gespeichert`)
return res.status(201).send({
success: true,
conversation_id: conversationId,
ticket_number: conversation?.ticket_number || conversation?.ticketNumber,
})
})
}
export default helpdeskInboundEmailRoutes