E-Mail Lesestatus und Ordnerhierarchie synchronisieren

KI-AGENT: Synchronisiert Gelesen/Ungelesen mit IMAP, gleicht vorhandene Nachrichten-Flags beim Sync ab und zeigt verschachtelte IMAP-Ordner unter ihren Elternordnern an.
This commit is contained in:
2026-05-23 20:13:35 +02:00
parent 347319aee3
commit 7239ad92e4
3 changed files with 254 additions and 19 deletions

View File

@@ -301,6 +301,25 @@ export function emailSyncService(server: FastifyInstance) {
return saved
}
const updateCachedMessageFlags = async (
mailboxId: string,
uid: number,
flags: string[],
) => {
await server.db
.update(emailMessages)
.set({
flags,
seen: flags.includes("\\Seen"),
flagged: flags.includes("\\Flagged"),
updatedAt: new Date(),
})
.where(and(
eq(emailMessages.mailboxId, mailboxId),
eq(emailMessages.uid, uid),
))
}
const syncMailboxMessages = async (
account: MailAccountConnection,
client: ImapFlow,
@@ -323,9 +342,23 @@ export function emailSyncService(server: FastifyInstance) {
const newUids = allUids
.filter((uid: number) => !state?.highestUid || uid > state.highestUid)
.slice(-limit)
const flagSyncUids = allUids.slice(-limit)
highestUid = Math.max(state?.highestUid || 0, ...newUids, 0)
if (flagSyncUids.length) {
for await (const message of client.fetch(flagSyncUids, {
uid: true,
flags: true,
}, { uid: true })) {
await updateCachedMessageFlags(
mailbox.id,
Number(message.uid),
flagsFromMessage(message.flags),
)
}
}
if (newUids.length) {
for await (const message of client.fetch(newUids, {
uid: true,
@@ -385,6 +418,67 @@ export function emailSyncService(server: FastifyInstance) {
}
}
const setMessageSeen = async (
tenantId: number,
userId: string,
messageId: string,
seen: boolean,
) => {
const rows = await server.db
.select()
.from(emailMessages)
.where(and(
eq(emailMessages.tenantId, tenantId),
eq(emailMessages.userId, userId),
eq(emailMessages.id, messageId),
))
.limit(1)
const message = rows[0]
if (!message) return null
const account = await getAccount(tenantId, userId, message.accountId)
if (!account) {
throw new Error("E-Mail Konto nicht gefunden")
}
const client = createClient(account)
await client.connect()
try {
const lock = await client.getMailboxLock(message.mailboxPath)
try {
await client.mailboxOpen(message.mailboxPath)
if (seen) {
await client.messageFlagsAdd(message.uid, ["\\Seen"], { uid: true })
} else {
await client.messageFlagsRemove(message.uid, ["\\Seen"], { uid: true })
}
} finally {
lock.release()
}
} finally {
await client.logout().catch(() => client.close())
}
const currentFlags = Array.isArray(message.flags) ? message.flags : []
const nextFlags = seen
? Array.from(new Set([...currentFlags, "\\Seen"]))
: currentFlags.filter((flag) => flag !== "\\Seen")
const [updated] = await server.db
.update(emailMessages)
.set({
flags: nextFlags,
seen,
updatedAt: new Date(),
})
.where(eq(emailMessages.id, messageId))
.returning()
return updated
}
const listMailboxes = async (tenantId: number, userId: string, accountId: string) => {
return await server.db
.select()
@@ -451,5 +545,6 @@ export function emailSyncService(server: FastifyInstance) {
listMailboxes,
listMessages,
getMessage,
setMessageSeen,
}
}