2026-07-01 15:41:27 +02:00
""" Mail plugin — IMAP/SMTP, threading, templates, rules, PGP, delegates. """
from __future__ import annotations
2026-07-15 20:28:11 +02:00
import asyncio
import logging
2026-07-15 21:00:32 +02:00
from typing import Any
2026-07-15 20:28:11 +02:00
2026-07-01 15:41:27 +02:00
from app . plugins . base import BasePlugin
2026-07-23 19:01:18 +02:00
from app . plugins . manifest import PluginManifest , PluginRouteDef , FrontendMenuItem , FrontendPageRoute , FrontendDetailTab , FrontendSettingsPage
2026-07-01 15:41:27 +02:00
2026-07-15 20:28:11 +02:00
logger = logging . getLogger ( __name__ )
async def _auto_sync_loop ( ) - > None :
2026-07-20 11:31:17 +02:00
""" Background loop: process pending sync queue, then sync all active mail accounts every 5 minutes. """
from app . plugins . builtins . mail . services import auto_sync_all_accounts , process_sync_queue
from app . core . db import get_session_factory
2026-07-15 20:28:11 +02:00
while True :
2026-07-20 11:31:17 +02:00
try :
factory = get_session_factory ( )
async with factory ( ) as db :
await process_sync_queue ( db )
await db . commit ( )
except Exception as exc :
logger . warning ( " process_sync_queue error: %s " , exc )
2026-07-15 20:28:11 +02:00
try :
await auto_sync_all_accounts ( )
except Exception as exc :
logger . warning ( " auto_sync error: %s " , exc )
2026-07-20 12:01:16 +02:00
await asyncio . sleep ( 60 )
2026-07-15 20:28:11 +02:00
2026-07-01 15:41:27 +02:00
class MailPlugin ( BasePlugin ) :
""" Mail plugin for email management: IMAP sync, SMTP send, threading, rules, PGP. """
2026-07-15 20:28:11 +02:00
_auto_sync_task : asyncio . Task | None = None
2026-07-01 15:41:27 +02:00
manifest = PluginManifest (
name = " mail " ,
2026-07-20 13:42:30 +02:00
version = " 1.3.0 " ,
2026-07-01 15:41:27 +02:00
display_name = " Mail " ,
description = (
" Email management: IMAP sync, SMTP send, threading, "
" templates, rules, vacation, PGP, delegates, labels. "
) ,
dependencies = [ ] ,
routes = [
PluginRouteDef (
path = " /api/v1/mail " ,
module = " app.plugins.builtins.mail.routes " ,
router_attr = " router " ,
) ,
] ,
events = [ ] ,
2026-07-24 10:37:44 +02:00
migrations = [ " 0001_initial.sql " , " 0006_flag_type.sql " , " 0007_sync_queue.sql " , " 0008_sync_queue_deleted_at.sql " , " 0009_remove_mail_soft_delete.sql " , " 0010_add_deleted_at.sql " ] ,
2026-07-16 00:34:36 +02:00
permissions = [ " mail:read " , " mail:send " , " mail:config " , " mail:share " , " mail:write " , " mail:delete " ] ,
2026-07-23 19:01:18 +02:00
menu_items = [
2026-07-24 22:04:45 +02:00
FrontendMenuItem ( label_key = ' nav.mail ' , label = ' E-Mail ' , path = ' /mail ' , icon = ' Mail ' , order = 30 ) ,
2026-07-23 19:01:18 +02:00
] ,
page_routes = [
FrontendPageRoute ( path = ' /mail ' , component = ' @/pages/Mail ' , protected = True ) ,
FrontendPageRoute ( path = ' /mail/settings ' , component = ' @/pages/MailSettings ' , protected = True ) ,
] ,
settings_pages = [
FrontendSettingsPage ( path = ' mail ' , label_key = ' settings.mail ' , label = ' Mail ' , component = ' @/pages/MailSettings ' , icon = ' Mail ' , order = 50 ) ,
] ,
detail_tabs = [
FrontendDetailTab ( entity_type = ' contact ' , label_key = ' tabs.email ' , label = ' E-Mails ' , component = ' @/components/contact/ContactMailTab ' , icon = ' Mail ' , order = 20 , permission = ' mail:read ' ) ,
] ,
2026-07-26 23:15:34 +02:00
author = " LeoCRM Team " ,
min_app_version = " 1.0.0 " ,
hooks = [ " mail.before_send " , " mail.after_send " ] ,
contract_version = " 1.0.0 " ,
2026-07-01 15:41:27 +02:00
)
2026-07-15 20:28:11 +02:00
async def on_activate (
self , db , service_container , event_bus
) - > None :
""" Activate plugin: register events + start auto-sync background task. """
await super ( ) . on_activate ( db , service_container , event_bus )
if self . _auto_sync_task is None or self . _auto_sync_task . done ( ) :
self . _auto_sync_task = asyncio . create_task ( _auto_sync_loop ( ) )
logger . info ( " Mail plugin: auto-sync background task started " )
2026-07-15 21:00:32 +02:00
def get_notification_types ( self ) - > list [ dict [ str , Any ] ] :
""" Return the notification types this mail plugin registers. """
return [
{ " type_key " : " mail_new " , " category " : " mail " , " label " : " Neue E-Mail empfangen " , " description " : " Benachrichtigung bei neuen E-Mails " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_error " , " category " : " mail " , " label " : " IMAP-Verbindungsfehler " , " description " : " Fehler bei der Verbindung zum Mailserver " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_auth " , " category " : " mail " , " label " : " IMAP-Login-Fehler " , " description " : " Anmeldung am Mailserver fehlgeschlagen " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_quota " , " category " : " mail " , " label " : " Postfach fast voll " , " description " : " Warnung bei hohem Postfach-Füllstand " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_sync_error " , " category " : " mail " , " label " : " Sync-Fehler " , " description " : " Synchronisierung fehlgeschlagen " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_sent " , " category " : " mail " , " label " : " E-Mail gesendet " , " description " : " Bestätigung beim Senden einer E-Mail " , " is_enabled_by_default " : False } ,
{ " type_key " : " mail_send_error " , " category " : " mail " , " label " : " SMTP-Sendefehler " , " description " : " E-Mail konnte nicht gesendet werden " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_draft " , " category " : " mail " , " label " : " Entwurf gespeichert " , " description " : " Bestätigung beim Speichern eines Entwurfs " , " is_enabled_by_default " : False } ,
{ " type_key " : " mail_account " , " category " : " mail " , " label " : " Account deaktiviert " , " description " : " Warnung bei deaktiviertem Mail-Account " , " is_enabled_by_default " : True } ,
{ " type_key " : " mail_folder " , " category " : " mail " , " label " : " Ordner erstellt/gelöscht " , " description " : " Bestätigung bei Ordner-Operationen " , " is_enabled_by_default " : False } ,
]
2026-07-15 20:28:11 +02:00
async def on_deactivate (
self , db , service_container , event_bus
) - > None :
""" Deactivate plugin: stop auto-sync task + unregister events. """
2026-07-26 23:15:34 +02:00
# Contract abmelden
from app . plugins . builtins . contracts import get_contract_registry
get_contract_registry ( ) . unregister ( self . manifest . name )
2026-07-15 20:28:11 +02:00
if self . _auto_sync_task is not None and not self . _auto_sync_task . done ( ) :
self . _auto_sync_task . cancel ( )
try :
await self . _auto_sync_task
except asyncio . CancelledError :
pass
self . _auto_sync_task = None
logger . info ( " Mail plugin: auto-sync background task stopped " )
await super ( ) . on_deactivate ( db , service_container , event_bus )