Files
mailadler/ERWEITERTE_FEATURES.md
2026-02-04 02:47:35 +01:00

16 KiB

Mail-Adler Erweiterte Features

1. Datenbank: SQLite vs. MariaDB

Analyse für ~3000 Mails

Kriterium SQLite MariaDB
Größe 1 Datei Server-basiert
3000 Mails Größe ~500MB-1GB ~100-200MB
Backup Einfach (Datei kopieren) ⚠️ MySQL-Dumps nötig
Verschlüsselung ⚠️ SQLCipher (extra) TLS ready
Aufbewahrungsfristen Einfach (SQL Trigger) Einfach (SQL Trigger)
Performance Gut für lokal ⚠️ Remote-Latenz
Installation Qt-built-in ⚠️ Server setup
Deployment Mit App Extern

EMPFEHLUNG: SQLite + SQLCipher (Phase B)

  • Lokal = privat + schnell
  • Einfaches Backup (Datei)
  • Encryption eingebaut
  • Keine Server-Abhängigkeit

MariaDB später (Phase E+) wenn Multi-User/Sync nötig


2. Gesetzliche Aufbewahrungsfristen + Auto-Löschung

Implementierung

# src/storage/retention_policy.py

class RetentionPolicy:
    def __init__(self):
        self.policies = {
            # Deutschland/EU (DSGVO)
            "GDPR": {
                "email": 7 * 365,  # 7 Jahre (falls geschäftsrelevant)
                "attachment": 7 * 365,
                "deleted_email": 30,  # Gelöschte 30 Tage
                "spam": 30,  # Spam 30 Tage
            },
            # Schweiz (StG)
            "CHE": {
                "email": 7 * 365,
                "attachment": 7 * 365,
                "deleted_email": 30,
                "spam": 30,
            },
            # USA (verschiedene Staaten)
            "USA": {
                "email": 5 * 365,  # 5 Jahre
                "attachment": 5 * 365,
                "deleted_email": 30,
                "spam": 30,
            },
        }
    
    def schedule_auto_delete(self):
        """
        Daily Job: Lösche alte Emails/Anhänge
        """
        scheduler.add_job(
            self.delete_old_emails,
            'cron',
            hour=3,  # 03:00 nachts
            minute=0
        )
    
    def delete_old_emails(self):
        """
        Lösche Emails älter als retention_days
        Speichere vorher Hash zum Audit
        """
        region = Settings.retentionRegion()  # GDPR, CHE, USA
        policy = self.policies[region]
        
        # Email löschen
        cutoff_date = datetime.now() - timedelta(days=policy["email"])
        old_emails = db.query(
            "SELECT id, subject, date FROM emails WHERE date < ? AND folder != 'trash'",
            cutoff_date
        )
        
        for email in old_emails:
            # Audit Log speichern (bevor löschen)
            audit_log.record({
                "email_id": email["id"],
                "subject": email["subject"],
                "deleted_at": datetime.now(),
                "reason": "retention_policy_auto_delete"
            })
            
            # Anhänge löschen
            db.delete_attachments(email["id"])
            
            # Email löschen
            db.delete_email(email["id"])
        
        log.info(f"Deleted {len(old_emails)} old emails")
        
        # Spam löschen (schneller)
        spam_cutoff = datetime.now() - timedelta(days=policy["spam"])
        db.delete_emails(f"folder = 'spam' AND date < ?", spam_cutoff)

UI: Aufbewahrungsrichtlinie einstellen

Einstellungen → Datenschutz

Aufbewahrungsrichtlinie:
├─ Land/Region: [GDPR - Deutschland/EU] ▼
│  └─ Emails: 7 Jahre
│  └─ Gelöschte: 30 Tage
│  └─ Spam: 30 Tage
│
├─ Auto-Löschung:
│  ☑ Aktiviert (täglich 03:00)
│  ☑ Audit-Log speichern
│
└─ Info: "Vollständige Compliance mit DSGVO"

3. Anhänge: Lazy-Load (Nur bei Anklick herunterladen)

Architektur

// src/attachment/AttachmentManager.h/cpp
class AttachmentManager {
private:
    struct AttachmentMetadata {
        QString id;
        QString filename;
        QString mime_type;
        int size;  // Bytes
        bool downloaded;  // false = noch nicht heruntergeladen
        QString local_path;  // "" wenn nicht downloaded
    };
    
public:
    // Zeige Anhang-Preview (nur Metadaten)
    QVector<AttachmentMetadata> getAttachmentsMetadata(QString emailId) {
        // Keine Daten herunterladen, nur Info:
        // Größe, Name, Typ anzeigen
    }
    
    // Download on Click
    void downloadAttachment(QString attachmentId) {
        // Erst wenn User klickt:
        // 1. Download vom Server
        // 2. In ~/.local/share/mail-adler/attachments/cache/
        // 3. Beim Anklick öffnen
    }
    
    // Auto-Cleanup (nach Öffnen)
    void autoCleanupOldAttachments() {
        // Nach 7 Tagen gelöschte Attachments aus Cache löschen
        // Originale bleiben im Email-Archive
    }
};

UI: Anhang-Anzeige

Email von alice@gmx.de

Subject: Dokumente für Projekt

Anhänge (3):
├─ 📄 Vertrag.pdf (2.3 MB) [⬇️ Download] [🔗 Öffnen]
├─ 📊 Budget.xlsx (1.2 MB) [⬇️ Download] [🔗 Öffnen]
└─ 🖼️ Logo.png (845 KB) [⬇️ Download] [🔗 Öffnen]

(Nur Name + Größe angezeigt, Download erst auf Klick)

4. UI: Ungelesene-Zähler + Navigation

Ordner-Panel mit Zähler

📧 Eingang (23)  ← 23 ungelesene
├─ 📂 Arbeit (8)
├─ 📂 Privat (5)
├─ 📂 Spam (10)
└─ 🗑️ Papierkorb (2)

[Gesendet]
[Entwürfe]
[Archiv]

Click auf "(23)" → Erste ungelesen

// src/ui/FolderPanel.h/cpp
void FolderPanel::onUnreadCountClicked(QString folder) {
    // 1. Hole erste ungelesene Email
    Email firstUnread = db.query(
        "SELECT * FROM emails WHERE folder = ? AND unread = 1 ORDER BY date DESC LIMIT 1",
        folder
    );
    
    // 2. Springe zu dieser Email
    emit navigateToEmail(firstUnread.id);
    
    // 3. Markiere als gelesen
    email.markAsRead();
    
    // 4. (Optional) Nächste ungelesen
    // Wenn User Taste drückt (z.B. 'n' für next unread)
}

void MailListView::onKeyPressed(Qt::Key key) {
    if (key == Qt::Key_N) {  // 'n' = next unread
        Email next = getNextUnreadInFolder();
        if (next.id()) {
            navigateToEmail(next.id);
            next.markAsRead();
        }
    }
}

Tastatur-Shortcuts für Ungelesene

n  → Nächste ungelesen
p  → Vorherige ungelesen
u  → Markiere als ungelesen
f  → Markiere als gelesen

Beispiel:
User klickt auf (23) → Erste ungelesen wird angezeigt
User drückt 'n' → Nächste ungelesen
User drückt 'n' → Nächste ungelesen
... etc

5. Ungelesene-Handling: Spam & Blockierte

Spam-Check

# src/email/UnreadHandler.py
class UnreadHandler:
    def categorizeUnread(self, email):
        """
        Prüfe: Ist ungelesene Email in Spam?
        Ist ungelesene Email blockiert?
        """
        
        # 1. Spam-Check
        if email.folder == "spam":
            return {
                "unread": True,
                "spam": True,
                "blocked": False,
                "action": "Nicht zählen in normalem Ungelesen"
            }
        
        # 2. Blockiert-Check
        sender = email.from_address
        if db.isBlocked(sender):
            return {
                "unread": True,
                "spam": False,
                "blocked": True,
                "action": "Nicht zählen in normalem Ungelesen"
            }
        
        # 3. Normal
        return {
            "unread": True,
            "spam": False,
            "blocked": False,
            "action": "Zähle in (23)"
        }

UI: Separate Zähler

📧 Eingang (23 normal) [🚫 5 blocked] [🚫 8 spam]
   └─ 23 = nur legitim ungelesen
   └─ 5 = blockierte Absender
   └─ 8 = Spam

Wenn User auf (23) klickt:
  → Erste legitim ungelesen
  
Wenn User auf [🚫 5] klickt:
  → Erste blockierte (aber nicht vorschalten)

6. Serienbriefe: Massenmails mit Vorlagen

Implementierung

// src/mail/MailMerge.h/cpp
class MailMerge {
public:
    struct Template {
        QString id;
        QString name;
        QString subject;  // Mit {{var}} Platzhaltern
        QString body;     // Mit {{var}} Platzhaltern
        QStringList variables;  // ["name", "email", "company"]
    };
    
    void createSeriesEmail(Template tmpl, QVector<QMap<QString, QString>> data) {
        """
        Erstelle Massen-Email aus Vorlage + Daten
        
        data = [
            {"name": "Alice", "email": "alice@...", "company": "ABC Ltd"},
            {"name": "Bob", "email": "bob@...", "company": "XYZ Corp"},
        ]
        
        Beispiel Vorlage:
        Subject: Hallo {{name}}!
        Body: Lieber {{name}}, 
              {{company}} hat sich für unsere Services interessiert...
        """
        
        for (auto &row : data) {
            // 1. Ersetze {{var}} durch Wert
            QString subject = tmpl.subject;
            QString body = tmpl.body;
            
            for (auto &[var, value] : row) {
                subject.replace("{{" + var + "}}", value);
                body.replace("{{" + var + "}}", value);
            }
            
            // 2. Erstelle Email
            Email email;
            email.to = row["email"];
            email.subject = subject;
            email.body = body;
            email.delayed = true;  // Verzögerter Versand
            
            // 3. Speichern
            m_pendingEmails.push_back(email);
        }
    }
};

UI: Serienbriefe Dialog

┌──────────────────────────────────┐
│ Serienbriefe                     │
├──────────────────────────────────┤
│ Vorlage: [Kundenangebot] ▼       │
│                                  │
│ Empfänger-Liste (CSV):           │
│ [Durchsuchen...]                 │
│ ✓ header row (name, email, co)   │
│                                  │
│ Preview:                         │
│ ┌────────────────────────────────┐
│ │Subject: Hallo Alice!           │
│ │                                │
│ │Lieber Alice, ABC Ltd hat...    │
│ └────────────────────────────────┘
│                                  │
│ Versand:                         │
│ ☑ Verzögerter Versand            │
│ └─ Nach Email: [1] Minute        │
│                                  │
│ [Preview] [Senden] [Abbrechen]   │
└──────────────────────────────────┘

7. Verzögertes Versenden (Scheduled Send)

Implementierung

// src/mail/DelayedSend.h/cpp
class DelayedSend {
public:
    struct ScheduledEmail {
        QString id;
        QString to;
        QString subject;
        QString body;
        QDateTime sendAt;  // Wann versenden
        QString status;  // "scheduled", "sending", "sent", "cancelled"
    };
    
    void scheduleEmail(Email email, QDateTime sendAt) {
        """
        Plane Email-Versand für später
        """
        ScheduledEmail scheduled;
        scheduled.id = generateId();
        scheduled.to = email.to;
        scheduled.subject = email.subject;
        scheduled.body = email.body;
        scheduled.sendAt = sendAt;
        scheduled.status = "scheduled";
        
        db.insert("scheduled_emails", scheduled);
        
        // Zeige Timer in Entwürfe
        emit scheduledEmailCreated(scheduled);
    }
    
    void checkAndSendScheduled() {
        """
        Alle 1 Minute prüfen: Welche Emails sind reif zum Versenden?
        """
        auto now = QDateTime::currentDateTime();
        
        auto ready = db.query(
            "SELECT * FROM scheduled_emails WHERE sendAt <= ? AND status = 'scheduled'",
            now
        );
        
        for (auto &email : ready) {
            sendEmail(email);
            db.update("scheduled_emails", email.id, {"status": "sent"});
        }
    }
};

UI: Entwürfe mit Timer

Entwürfe (3)

[📝 Kundenangebot für Alice]
├─ Status: Versand geplant
├─ Versend um: 2025-02-05 14:30
├─ Countdown: 2h 15min
└─ [❌ Abbrechen] [✏️ Bearbeiten]

[📝 Besprechungsnotizen]
├─ Status: Normal (nicht geplant)

[📝 Test Email]
├─ Status: Fehler beim Versand
├─ Fehler: "SMTP Error 550"
└─ [🔄 Erneut versuchen] [Löschen]

Versand abbrechen:
┌──────────────────────────┐
│ Email wird versendet in:  │
│                          │
│ ⏱ [████░░░░░░░░░] 30s   │
│                          │
│ [❌ Jetzt abbrechen]      │
│ [▶️ Weiter mit 'E']      │
└──────────────────────────┘

8. AD/Microsoft Integration (Phase D+)

LDAP + Outlook Sync

# src/integration/MicrosoftAD.py

class MicrosoftADIntegration:
    def __init__(self):
        self.ldap_server = "ldap://ad.company.com"
        self.graph_api = "https://graph.microsoft.com/v1.0"
    
    def syncContacts(self):
        """
        Hole Kontakte aus AD LDAP
        """
        conn = ldap.initialize(self.ldap_server)
        results = conn.search_s(
            "OU=Users,DC=company,DC=com",
            ldap.SCOPE_SUBTREE,
            "(objectClass=person)",
            ['mail', 'displayName', 'telephoneNumber']
        )
        
        # Speichere in lokale Kontakt-Datenbank
        for dn, attrs in results:
            contact = {
                "name": attrs['displayName'][0],
                "email": attrs['mail'][0],
                "phone": attrs.get('telephoneNumber', [''])[0],
                "source": "AD"
            }
            db.save_contact(contact)
    
    def syncCalendar(self, user_email):
        """
        Hole Outlook-Kalender über MS Graph API
        """
        # Benötigt OAuth2 Token
        headers = {"Authorization": f"Bearer {self.get_token()}"}
        
        response = requests.get(
            f"{self.graph_api}/me/events",
            headers=headers
        )
        
        for event in response.json()['value']:
            cal_event = {
                "title": event['subject'],
                "start": event['start']['dateTime'],
                "end": event['end']['dateTime'],
                "organizer": event['organizer']['emailAddress']['address'],
                "source": "outlook"
            }
            db.save_calendar_event(cal_event)
    
    def showADAvailability(self, email):
        """
        Zeige AD-User Verfügbarkeit im Kalender
        """
        # Prüfe: Ist User frei/busy?
        # Zeige in Kalender-UI

UI: AD-Integration

Kontakte

Quelle: [Alle] [Lokal] [AD] [Outlook]

Alice Schmidt (AD)
├─ Email: alice@company.de
├─ Phone: +49-30-12345678
├─ Verfügbar: 10:30-11:30 (aus Outlook)
└─ [Termin vereinbaren]

Bob Müller (AD)
├─ Email: bob@company.de
├─ Verfügbar: Ganztag frei
└─ [Termin vereinbaren]

9. Zusammenfassung: Erweiterte Features

Feature Phase Priorität Komplexität
SQLite → MariaDB E+ Niedrig Hoch
Aufbewahrungsfristen B+ Hoch Mittel
Lazy-Load Anhänge B+ Hoch Mittel
Ungelesene-Zähler B+ Hoch Niedrig
Serienbriefe C Mittel Hoch
Verzögerter Versand C Mittel Mittel
AD/Outlook Integration D+ Niedrig Hoch

MVP (Must-Have Phase B+):

  1. SQLite mit SQLCipher
  2. Aufbewahrungsfristen (Auto-Löschung)
  3. Lazy-Load Anhänge
  4. Ungelesene-Zähler + Navigation

Nice-to-Have (Phase C+):

  1. Serienbriefe
  2. Verzögerter Versand
  3. AD Integration