Donnerstagabend, kurz vor sieben. Outlook verbindet sich nicht mehr. Teams auch nicht. Am Freitagmorgen kommt dann die Frage, die mit dem Ausfall selbst wenig zu tun hat und trotzdem die wichtigste ist: „Wenn das nicht wiederkommt, haben wir unsere E-Mails dann eigentlich noch?“ Wer seine E-Mails selbst sichern will, braucht dafür kein neues Abo. Microsoft Access, ein paar Zeilen VBA und eine externe Festplatte reichen.
Bei mir lief Outlook am Freitagnachmittag wieder. Meine erste Amtshandlung danach war kein Kaffee, sondern ein Backup.
Was am 12. und 13. November passiert ist
Ab Donnerstagabend, kurz vor 19 Uhr, waren Outlook, Teams, OneDrive und weitere Dienste aus Microsoft 365 weltweit gestört. Microsoft sprach von einer Überlastung der Infrastruktur in vielen Regionen. Auf Downdetector liefen gegen 19 Uhr knapp 130.000 Störungsmeldungen allein zu Outlook auf. Nach Microsofts Angaben gab es keine Hinweise auf einen Angriff. Bis in den Freitag hinein blieb die Lage instabil.
Es war nicht der erste Ausfall in diesem Jahr. Schon Ende August waren Microsofts E-Mail-Dienste stundenlang nicht erreichbar.
Ein Ausfall ist noch kein Datenverlust. Aber er zeigt dir sehr zuverlässig, ob du ohne deinen Anbieter an deine eigenen Daten kommst.
Microsoft sichert deine E-Mails nicht
Microsoft betreibt eine hochverfügbare Cloud. Fällt ein Rechenzentrum aus, springt ein anderes ein. Das ist Redundanz, kein Backup. Im sogenannten Shared Responsibility Model ist die Aufteilung klar geregelt: Microsoft ist für die Verfügbarkeit der Dienste zuständig, du für deine Daten. Ausführlich habe ich das im Beitrag Microsoft sichert deine M365-Daten nicht beschrieben.
Microsoft sagt das übrigens selbst. Im Microsoft-Servicevertrag steht sinngemäß, dass du bei einem Ausfall möglicherweise nicht an deine gespeicherten Inhalte kommst. Direkt danach folgt die Empfehlung, deine Daten regelmäßig zu sichern.
Ein Backup ist eine Kopie deiner Daten, die unabhängig vom Original erreichbar ist. Ein Papierkorb mit 14 bis 93 Tagen Aufbewahrungsfrist ist keins.
Und das kostenpflichtige Microsoft 365 Backup?
Seit Juli 2024 bietet Microsoft mit „Microsoft 365 Backup“ tatsächlich eine eigene Sicherung an. Sie ist nicht im normalen Abo enthalten, sondern wird nach Datenmenge abgerechnet: Listenpreis 0,15 US-Dollar pro Gigabyte und Monat. Die Sicherungen liegen innerhalb von Microsofts eigener Cloud, in den Rechenzentren der jeweiligen Region.
Das ist praktisch, wenn jemand versehentlich einen Ordner löscht oder ein Postfach verschlüsselt wird. Bei einem Ausfall wie diesem hilft es dir wenig. Das Backup wohnt im selben Haus wie das Original. Wenn Microsoft den Zugang zu Outlook nicht hinbekommt, ist nicht garantiert, dass du in dem Moment an die Sicherung kommst.
Ein Backup beim selben Anbieter schützt dich vor deinen eigenen Fehlern. Vor den Fehlern des Anbieters schützt es dich nicht.
Outlook-E-Mails sichern mit Access: so sieht das aus
Die pragmatische Variante läuft auf dem Rechner, auf dem Outlook sowieso installiert ist. Eine kleine Access-Datenbank steuert Outlook fern und macht Folgendes:
- Sie fragt einmal nach dem Zielordner, zum Beispiel einer externen Festplatte oder einem Ordner auf dem NAS.
- Sie geht jedes eingebundene Postfach und jeden Mail-Ordner durch.
- Sie speichert jede E-Mail als eigene .msg-Datei, benannt nach Schema „Datum Absendername ‹absender@firma.de› Betreff“.
- Sie spiegelt die Ordnerstruktur aus Outlook auf der Festplatte: Postfach, Posteingang, Unterordner.
- Sie merkt sich in einer Tabelle, welche Mails schon gesichert sind. Beim nächsten Lauf kommen nur die neuen dazu.
Den wöchentlichen Lauf übernimmt die Windows-Aufgabenplanung. Access startet, sichert, schließt sich wieder. Eine Zusatzbibliothek wie Redemption ist dafür nicht nötig.
Warum .msg? Weil sich jede Datei per Doppelklick öffnen lässt, mit allen Anhängen. Und weil du sie per Drag-and-drop wieder in Outlook ziehen kannst. Die eckigen Klammern um die Mailadresse sind übrigens keine Spielerei: Die Zeichen < und > sind in Windows-Dateinamen verboten, ‹ und › nicht.
Bei mir waren das 12 Postfächer mit zusammen über 40.000 E-Mails. Der erste Lauf hat 45 Minuten gedauert und 7 GB auf der Platte belegt. Der wöchentliche Lauf danach braucht deutlich weniger Zeit.
Ein angenehmer Nebeneffekt: Die Sicherung arbeitet mit dem lokalen Zwischenspeicher von Outlook. Was Outlook bereits heruntergeladen hat, lässt sich auch dann sichern, wenn Microsofts Server gerade nicht antworten.
Warum nicht einfach eine PST-Datei exportieren?
Naheliegend wäre der Export über das Outlook-Menü in eine PST-Datei. Den habe ich bewusst nicht gewählt. Eine PST ist eine einzige große Datei, schnell mehrere Gigabyte schwer. Ist sie beschädigt, ist alles weg. Sie wächst bei jedem Export komplett neu, statt nur die neuen Mails aufzunehmen. Und ohne Outlook kommst du nicht hinein.
Einzelne .msg-Dateien in einer sauberen Ordnerstruktur kannst du mit dem Windows-Explorer durchsuchen. Eine defekte Datei betrifft genau eine E-Mail. Und die Access-Tabelle dahinter ist gleichzeitig dein Protokoll: Wann wurde was gesichert, wohin, wie viele pro Lauf.
Ein Backup, das du ohne die Software des Herstellers nicht lesen kannst, ist nur ein halbes Backup.
Wo dieser Ansatz an Grenzen stößt
Damit kein falscher Eindruck entsteht, die Einschränkungen gehören dazu:
- Nur das klassische Outlook. Das „neue Outlook“ bietet keine Schnittstelle für Fernsteuerung. Microsoft drängt die Anwender gerade dorthin. Diese Sicherung hat also ein Haltbarkeitsdatum, wenn auch kein kurzes.
- Nur E-Mails. Kalender, Kontakte, Teams-Chats und OneDrive-Dateien sichert sie nicht.
- Nur, was lokal ist. Online-Archive, die Outlook nicht zwischenspeichert, bleiben außen vor.
- Sicherheitsabfrage. Erkennt Outlook keinen aktuellen Virenschutz, fragt es bei jedem Zugriff nach. Das lässt sich im Trust Center prüfen.
- Kein revisionssicheres Archiv. Ein Ordner voller .msg-Dateien erfüllt nicht die Anforderungen an eine unveränderbare E-Mail-Archivierung nach GoBD. Für steuerrelevante Korrespondenz brauchst du zusätzlich ein echtes Archivsystem.
- Eine Kopie ist noch keine Strategie. Hängt die Festplatte dauerhaft am Rechner, verschlüsselt Ransomware sie gleich mit. Die 3-2-1-Regel gilt auch hier: drei Kopien, zwei Medien, eine außer Haus.
Was das für dich als Geschäftsführer bedeutet
Du musst diesen Code nicht verstehen. Aber stell deiner IT nach diesem Wochenende eine einzige Frage: „Wenn Microsoft drei Tage weg ist, wie komme ich an die Mail mit dem Angebot vom letzten Dienstag?“
Lautet die Antwort „gar nicht“, hast du kein technisches Problem, sondern ein betriebliches. Dann hängt dein Vertrieb, deine Auftragsannahme und im Zweifel deine Nachweispflicht an der Verfügbarkeit eines einzigen Anbieters. Eine lokale Kopie der E-Mails ist kein Ersatz für ein durchdachtes Backup-Konzept. Sie ist aber ein schneller erster Schritt, der kein Abo kostet und mit Werkzeugen läuft, die in den meisten Betrieben ohnehin vorhanden sind.
Für die Technik: der Code
Das Werkzeug besteht aus einer Access-Datenbank mit vier Tabellen und einem VBA-Modul. Es arbeitet mit dem klassischen Outlook per Late Binding, also ohne Verweis auf die Outlook-Bibliothek und ohne Zusatzkomponenten wie Redemption.
Option Compare Database
Option Explicit
'==============================================================================
' modMailBackup
' Sichert alle Outlook-Postfächer als .msg (klassisches Outlook, Late Binding,
' ohne Redemption). Einstiege:
' DatenmodellAnlegen - legt Tabellen an bzw. ergänzt/migriert sie
' KomplettStart - Zielpfad wählen, alle Ordner vollständig durchgehen
' FolgeStart - letzter Zielpfad, nur Mails seit letztem OK-Lauf
' FolgeStartAuto - Wrapper für Makro /x (AusführenCode braucht Function)
'==============================================================================
'--- Outlook-Konstanten
Private Const olMSGUnicode As Long = 9
Private Const olMail As Long = 43
Private Const olMailItem As Long = 0
Private Const PR_SENDER_SMTP As String = "http://schemas.microsoft.com/mapi/proptag/0x5D01001F"
Private Const PR_MSG_ID As String = "http://schemas.microsoft.com/mapi/proptag/0x1035001F"
'--- Steuerung
Private Const MAX_FEHLER As Long = 200 ' danach Abbruch (z. B. Platte voll)
Private Const UEBERLAPPUNG_TAGE As Long = 1 ' Sicherheitsabstand beim Folgelauf
'--- Laufzeitstatus
Private mdb As DAO.Database
Private mrsLog As DAO.Recordset
Private mrsFehler As DAO.Recordset
Private mfso As Object
Private mlngLauf As Long
Private mlngAnzahl As Long
Private mlngFehler As Long
Private mbAbbruch As Boolean
Private mstrStore As String
Private mstrOrdner As String
'==============================================================================
' 1) Datenmodell
'==============================================================================
Public Sub DatenmodellAnlegen(Optional ByVal bStill As Boolean = False)
Dim db As DAO.Database
On Error GoTo Fehler
Set db = CurrentDb
'--- Gesicherte Mails (Schlüssel = Internet-Message-ID bzw. EntryID)
If Not TabelleExistiert(db, "tblMailBackup") Then
db.Execute "CREATE TABLE tblMailBackup (" & _
"MsgKey TEXT(255) CONSTRAINT PrimaryKey PRIMARY KEY, " & _
"Pfad MEMO, Gesichert DATETIME, LaufID LONG)", dbFailOnError
Else
FeldErgaenzen db, "tblMailBackup", "LaufID", "LONG"
End If
'--- Läufe
If Not TabelleExistiert(db, "tblBackupLauf") Then
db.Execute "CREATE TABLE tblBackupLauf (" & _
"LaufID COUNTER CONSTRAINT PrimaryKey PRIMARY KEY, " & _
"Modus TEXT(20), Start DATETIME, Ende DATETIME, Ziel TEXT(255), " & _
"Anzahl LONG, Fehler LONG, Status TEXT(20))", dbFailOnError
Else
FeldErgaenzen db, "tblBackupLauf", "Modus", "TEXT(20)"
FeldErgaenzen db, "tblBackupLauf", "Fehler", "LONG"
If FeldErgaenzen(db, "tblBackupLauf", "Status", "TEXT(20)") Then
' Migration aus Version 1: Erfolg (Ja/Nein) -> Status
If FeldExistiert(db, "tblBackupLauf", "Erfolg") Then
db.Execute "UPDATE tblBackupLauf SET Status = IIf(Erfolg, 'OK', 'Abgebrochen') " & _
"WHERE Status Is Null", dbFailOnError
End If
End If
End If
'--- Fehlerprotokoll
If Not TabelleExistiert(db, "tblBackupFehler") Then
db.Execute "CREATE TABLE tblBackupFehler (" & _
"FehlerID COUNTER CONSTRAINT PrimaryKey PRIMARY KEY, " & _
"LaufID LONG, Zeitpunkt DATETIME, Prozedur TEXT(64), " & _
"FehlerNr LONG, FehlerText MEMO, Store TEXT(255), Ordner MEMO, " & _
"Betreff TEXT(255), MsgKey TEXT(255), Datei MEMO)", dbFailOnError
db.Execute "CREATE INDEX idxFehlerLauf ON tblBackupFehler (LaufID)", dbFailOnError
End If
'--- Ausgeschlossene Stores (z. B. Online-Archiv, Öffentliche Ordner)
If Not TabelleExistiert(db, "tblStoreAusschluss") Then
db.Execute "CREATE TABLE tblStoreAusschluss (" & _
"StoreName TEXT(255) CONSTRAINT PrimaryKey PRIMARY KEY)", dbFailOnError
End If
db.TableDefs.Refresh
Exit Sub
Fehler:
Debug.Print Now, "DatenmodellAnlegen", Err.Number, Err.Description
If Not bStill Then MsgBox "Datenmodell konnte nicht angelegt werden:" & vbCrLf & _
Err.Number & " - " & Err.Description, vbCritical, "Mail-Backup"
End Sub
'==============================================================================
' 2) Komplettlauf: Zielpfad abfragen, alles durchgehen (Duplikate werden übersprungen)
'==============================================================================
Public Sub KomplettStart()
Dim strZiel As String
DatenmodellAnlegen
strZiel = ZielpfadWaehlen(LetzterZielpfad())
If Len(strZiel) = 0 Then Exit Sub
BackupAusfuehren strZiel, "Komplett", 0, False
End Sub
'==============================================================================
' 3) Folgelauf: letzter Zielpfad, nur Mails seit letztem OK-Lauf (minus Überlappung)
'==============================================================================
Public Sub FolgeStart(Optional ByVal bStill As Boolean = False)
Dim strZiel As String, dtAb As Date
DatenmodellAnlegen bStill
strZiel = LetzterZielpfad()
If Len(strZiel) = 0 Then
If bStill Then
' kein Dialog möglich: Lauf wird als abgebrochen protokolliert
BackupAusfuehren "", "Folge", 0, True
Exit Sub
End If
strZiel = ZielpfadWaehlen("")
If Len(strZiel) = 0 Then Exit Sub
End If
dtAb = Nz(DMax("Start", "tblBackupLauf", "Status='OK'"), 0)
If dtAb > 0 Then dtAb = dtAb - UEBERLAPPUNG_TAGE ' 0 = wirkt wie Komplettlauf
BackupAusfuehren strZiel, "Folge", dtAb, bStill
End Sub
'--- Für die Aufgabenplanung: Makro mcrMailBackup -> AusführenCode FolgeStartAuto() -> Beenden
Public Function FolgeStartAuto() As Boolean
FolgeStart True
FolgeStartAuto = True
End Function
'==============================================================================
' Kern
'==============================================================================
Private Sub BackupAusfuehren(ByVal strZiel As String, ByVal strModus As String, _
ByVal dtAb As Date, ByVal bStill As Boolean)
Dim olApp As Object, ns As Object, st As Object
Dim strStatus As String
On Error GoTo Fehler
Set mdb = CurrentDb
Set mfso = CreateObject("Scripting.FileSystemObject")
mlngLauf = 0: mlngAnzahl = 0: mlngFehler = 0: mbAbbruch = False
mstrStore = "": mstrOrdner = ""
mlngLauf = LaufAnlegen(strModus, strZiel)
Set mrsFehler = mdb.OpenRecordset("tblBackupFehler", dbOpenDynaset, dbAppendOnly)
Set mrsLog = mdb.OpenRecordset("tblMailBackup", dbOpenTable)
mrsLog.Index = "PrimaryKey"
If Len(strZiel) = 0 Then _
Err.Raise vbObjectError + 1000, , "Kein Zielpfad hinterlegt. Bitte einmal KomplettStart ausführen."
If Not mfso.FolderExists(strZiel) Then _
Err.Raise vbObjectError + 1001, , "Zielpfad nicht erreichbar (Laufwerk getrennt?): " & strZiel
Set olApp = OutlookHolen()
If olApp Is Nothing Then _
Err.Raise vbObjectError + 1002, , "Outlook nicht verfügbar. Klassisches Outlook installiert und Profil angemeldet?"
Set ns = olApp.GetNamespace("MAPI")
For Each st In ns.Stores
If mbAbbruch Then Exit For
StoreSichern st, strZiel, dtAb
Next
If mbAbbruch Then
FehlerLog "BackupAusfuehren", 0, "Abbruch nach " & MAX_FEHLER & " Fehlern."
strStatus = "Abgebrochen"
ElseIf mlngFehler > 0 Then
strStatus = "Mit Fehlern"
Else
strStatus = "OK"
End If
Abschluss:
On Error Resume Next
LaufAbschliessen strStatus
If Not bStill Then
MsgBox "Lauf " & mlngLauf & " (" & strModus & "): " & strStatus & vbCrLf & _
mlngAnzahl & " Mails gesichert, " & mlngFehler & " Fehler." & _
IIf(mlngFehler > 0, vbCrLf & "Details in tblBackupFehler.", ""), _
IIf(strStatus = "OK", vbInformation, vbExclamation), "Mail-Backup"
End If
ModulAufraeumen
Set st = Nothing: Set ns = Nothing: Set olApp = Nothing
Exit Sub
Fehler:
FehlerLog "BackupAusfuehren", Err.Number, Err.Description
strStatus = "Abgebrochen"
Resume Abschluss
End Sub
Private Sub StoreSichern(st As Object, ByVal strZiel As String, ByVal dtAb As Date)
On Error GoTo Fehler
mstrOrdner = ""
mstrStore = st.DisplayName
If DCount("*", "tblStoreAusschluss", _
"StoreName='" & Replace(mstrStore, "'", "''") & "'") > 0 Then Exit Sub
OrdnerSichern st.GetRootFolder, strZiel & "\" & Bereinigen(mstrStore), dtAb
Exit Sub
Fehler:
FehlerLog "StoreSichern", Err.Number, Err.Description
End Sub
Private Sub OrdnerSichern(fld As Object, ByVal strPfad As String, ByVal dtAb As Date)
Dim itms As Object, itm As Object, fldSub As Object
Dim strOrdner As String, i As Long, n As Long
On Error GoTo FehlerOrdner
strOrdner = fld.FolderPath
mstrOrdner = strOrdner
If fld.DefaultItemType = olMailItem Then
Set itms = fld.Items
If dtAb > 0 Then
Set itms = itms.Restrict("[ReceivedTime] >= '" & Format(dtAb, "ddddd hh:nn") & "'")
End If
n = itms.Count
For i = 1 To n
If mbAbbruch Then Exit Sub
Set itm = ElementHolen(itms, i)
If Not itm Is Nothing Then MailSichern itm, strPfad
Set itm = Nothing
If i Mod 50 = 0 Then DoEvents
Next
End If
Unterordner:
On Error GoTo FehlerUnterordner
For Each fldSub In fld.Folders
If mbAbbruch Then Exit Sub
OrdnerSichern fldSub, strPfad & "\" & Bereinigen(fldSub.Name), dtAb
mstrOrdner = strOrdner ' Kontext nach Rückkehr wiederherstellen
Next
Exit Sub
FehlerOrdner:
FehlerLog "OrdnerSichern", Err.Number, Err.Description
Resume Unterordner
FehlerUnterordner:
FehlerLog "OrdnerSichern/Unterordner", Err.Number, Err.Description
End Sub
Private Function ElementHolen(itms As Object, ByVal i As Long) As Object
On Error GoTo Fehler
Set ElementHolen = itms(i)
Exit Function
Fehler:
FehlerLog "ElementHolen", Err.Number, Err.Description, "Element " & i
End Function
Private Sub MailSichern(itm As Object, ByVal strPfad As String)
Dim strKey As String, strBasis As String, strDatei As String
Dim strBetreff As String, lngMax As Long, n As Long
On Error GoTo Fehler
If itm.Class <> olMail Then Exit Sub
strBetreff = itm.Subject
strKey = Left$(MsgKey(itm), 255)
mrsLog.Seek "=", strKey
If Not mrsLog.NoMatch Then Exit Sub
strBasis = Format(itm.ReceivedTime, "yyyy-mm-dd hhnn") & " " & _
Bereinigen(itm.SenderName) & " " & ChrW(&H2039) & _
Bereinigen(AbsenderSMTP(itm)) & ChrW(&H203A) & " " & _
Bereinigen(strBetreff)
lngMax = 245 - Len(strPfad) - 1 - 4 - 4 ' Pfad, "\", " (n)", ".msg"
If lngMax < 20 Then lngMax = 20
strBasis = Trim$(Left$(strBasis, lngMax))
OrdnerAnlegen strPfad
strDatei = strPfad & "\" & strBasis & ".msg"
Do While mfso.FileExists(strDatei)
n = n + 1
strDatei = strPfad & "\" & strBasis & " (" & n & ").msg"
Loop
itm.SaveAs strDatei, olMSGUnicode
mrsLog.AddNew
mrsLog!MsgKey = strKey
mrsLog!Pfad = strDatei
mrsLog!Gesichert = Now
mrsLog!LaufID = mlngLauf
mrsLog.Update
mlngAnzahl = mlngAnzahl + 1
Exit Sub
Fehler:
FehlerLog "MailSichern", Err.Number, Err.Description, strBetreff, strKey, strDatei
Resume Aufraeumen
Aufraeumen:
On Error Resume Next
If mrsLog.EditMode <> dbEditNone Then mrsLog.CancelUpdate
End Sub
'==============================================================================
' Protokoll
'==============================================================================
Private Function LaufAnlegen(ByVal strModus As String, ByVal strZiel As String) As Long
Dim rs As DAO.Recordset
Set rs = mdb.OpenRecordset("tblBackupLauf", dbOpenDynaset)
rs.AddNew
rs!Modus = strModus
rs!Start = Now
rs!Ziel = Left$(strZiel, 255)
rs!Anzahl = 0
rs!Fehler = 0
rs!Status = "Läuft"
LaufAnlegen = rs!LaufID
rs.Update
rs.Close
End Function
Private Sub LaufAbschliessen(ByVal strStatus As String)
On Error Resume Next
If mlngLauf = 0 Then Exit Sub
mdb.Execute "UPDATE tblBackupLauf SET Ende=Now(), Anzahl=" & mlngAnzahl & _
", Fehler=" & mlngFehler & ", Status='" & strStatus & "' " & _
"WHERE LaufID=" & mlngLauf, dbFailOnError
End Sub
Private Sub FehlerLog(ByVal strProzedur As String, ByVal lngNr As Long, ByVal strText As String, _
Optional ByVal strBetreff As String = "", Optional ByVal strKey As String = "", _
Optional ByVal strDatei As String = "")
On Error Resume Next
mlngFehler = mlngFehler + 1
Debug.Print Now, strProzedur, lngNr, strText, strBetreff
If Not mrsFehler Is Nothing Then
mrsFehler.AddNew
mrsFehler!LaufID = mlngLauf
mrsFehler!Zeitpunkt = Now
mrsFehler!Prozedur = Left$(strProzedur, 64)
mrsFehler!FehlerNr = lngNr
mrsFehler!FehlerText = strText
mrsFehler!Store = Left$(mstrStore, 255)
mrsFehler!Ordner = mstrOrdner
mrsFehler!Betreff = Left$(strBetreff, 255)
mrsFehler!MsgKey = Left$(strKey, 255)
mrsFehler!Datei = strDatei
mrsFehler.Update
If Err.Number <> 0 Then mrsFehler.CancelUpdate
End If
If mlngFehler >= MAX_FEHLER Then mbAbbruch = True
End Sub
Private Sub ModulAufraeumen()
On Error Resume Next
If Not mrsLog Is Nothing Then mrsLog.Close
If Not mrsFehler Is Nothing Then mrsFehler.Close
Set mrsLog = Nothing
Set mrsFehler = Nothing
Set mfso = Nothing
End Sub
'==============================================================================
' Hilfsfunktionen
'==============================================================================
Private Function OutlookHolen() As Object
Dim olApp As Object
On Error Resume Next
Set olApp = GetObject(, "Outlook.Application")
If olApp Is Nothing Then Set olApp = CreateObject("Outlook.Application")
Set OutlookHolen = olApp
End Function
Private Function MsgKey(itm As Object) As String
On Error Resume Next
MsgKey = itm.PropertyAccessor.GetProperty(PR_MSG_ID)
If Len(MsgKey) = 0 Then MsgKey = "EID:" & itm.EntryID
End Function
Private Function AbsenderSMTP(itm As Object) As String
On Error Resume Next
If itm.SenderEmailType = "EX" Then
AbsenderSMTP = itm.PropertyAccessor.GetProperty(PR_SENDER_SMTP)
If Len(AbsenderSMTP) = 0 Then _
AbsenderSMTP = itm.Sender.GetExchangeUser.PrimarySmtpAddress
Else
AbsenderSMTP = itm.SenderEmailAddress
End If
If Len(AbsenderSMTP) = 0 Then AbsenderSMTP = "unbekannt"
End Function
Private Function Bereinigen(ByVal s As String) As String
Dim i As Long, c As String
For i = 1 To Len(s)
c = Mid$(s, i, 1)
If AscW(c) < 32 Or InStr("\/:*?""<>|", c) > 0 Then Mid$(s, i, 1) = "_"
Next
Do While InStr(s, " ") > 0: s = Replace(s, " ", " "): Loop
s = Trim$(s)
Do While Len(s) > 0 And (Right$(s, 1) = "." Or Right$(s, 1) = " ")
s = Left$(s, Len(s) - 1)
Loop
If Len(s) = 0 Then s = "_"
Bereinigen = s
End Function
Private Sub OrdnerAnlegen(ByVal strPfad As String)
If mfso.FolderExists(strPfad) Then Exit Sub
OrdnerAnlegen mfso.GetParentFolderName(strPfad)
mfso.CreateFolder strPfad
End Sub
Private Function ZielpfadWaehlen(ByVal strStart As String) As String
With Application.FileDialog(4) ' msoFileDialogFolderPicker
.Title = "Zielordner für Mail-Backup"
If Len(strStart) > 0 Then .InitialFileName = strStart & "\"
If .Show Then ZielpfadWaehlen = .SelectedItems(1)
End With
End Function
Private Function LetzterZielpfad() As String
LetzterZielpfad = Nz(DLookup("Ziel", "tblBackupLauf", "LaufID=" & _
Nz(DMax("LaufID", "tblBackupLauf", "Ziel Is Not Null AND Ziel<>''"), 0)), "")
End Function
Private Function TabelleExistiert(db As DAO.Database, ByVal strTabelle As String) As Boolean
Dim tdf As DAO.TableDef
db.TableDefs.Refresh
For Each tdf In db.TableDefs
If StrComp(tdf.Name, strTabelle, vbTextCompare) = 0 Then
TabelleExistiert = True
Exit Function
End If
Next
End Function
Private Function FeldExistiert(db As DAO.Database, ByVal strTabelle As String, _
ByVal strFeld As String) As Boolean
Dim fld As DAO.Field
db.TableDefs.Refresh
db.TableDefs(strTabelle).Fields.Refresh
For Each fld In db.TableDefs(strTabelle).Fields
If StrComp(fld.Name, strFeld, vbTextCompare) = 0 Then
FeldExistiert = True
Exit Function
End If
Next
End Function
Private Function FeldErgaenzen(db As DAO.Database, ByVal strTabelle As String, _
ByVal strFeld As String, ByVal strTyp As String) As Boolean
If FeldExistiert(db, strTabelle, strFeld) Then Exit Function
db.Execute "ALTER TABLE [" & strTabelle & "] ADD COLUMN [" & strFeld & "] " & strTyp, dbFailOnError
db.TableDefs.Refresh
FeldErgaenzen = True
End Function
Die öffentlichen Einstiege
Das Modul hat drei öffentliche Einstiege. DatenmodellAnlegen legt die Tabellen an. KomplettStart fragt nach dem Zielordner und geht alle Postfächer vollständig durch. FolgeStart nimmt den zuletzt verwendeten Zielordner und sichert nur, was seit dem letzten erfolgreichen Lauf dazugekommen ist.
Die Konstanten oben im Modul steuern das Verhalten. MAX_FEHLER begrenzt, nach wie vielen Fehlern ein Lauf abbricht. UEBERLAPPUNG_TAGE legt fest, wie weit ein Folgelauf vor den letzten erfolgreichen Lauf zurückgreift.
Das Datenmodell
Zuerst legt DatenmodellAnlegen die Tabellen an:
tblMailBackupmerkt sich jede gesicherte Mail.tblBackupLaufprotokolliert jeden Lauf.tblBackupFehlerhält jeden Fehler mit Kontext fest.tblStoreAusschlussnimmt Postfächer auf, die übersprungen werden sollen.
Die Prozedur darf beliebig oft laufen. Fehlende Tabellen legt sie an, fehlende Spalten ergänzt sie. Eine ältere Version des Datenmodells wird dabei automatisch angehoben.
Der Schlüssel in tblMailBackup ist die Internet-Message-ID der Mail. Sie bleibt gleich, wenn eine Mail in Outlook in einen anderen Ordner verschoben wird. Die interne EntryID von Outlook tut das nicht.
Komplettlauf und Folgelauf
Der Komplettlauf ist für den ersten Start gedacht und für eine gelegentliche Kontrolle, etwa einmal im Monat.
Auch der Komplettlauf erzeugt keine Duplikate. Bereits gesicherte Mails erkennt er an der Message-ID und überspringt sie. Er findet aber Mails, die nachträglich in alte Ordner verschoben wurden und deshalb einem reinen Datumsfilter entgehen würden.
Der Folgelauf ist der wöchentliche Normalfall. Er filtert in Outlook nach dem Empfangsdatum und braucht deshalb nur einen Bruchteil der Zeit.
Der Folgelauf rechnet nur ab dem letzten Lauf mit Status “OK” weiter. Ist eine Mail einmal gescheitert, kommt sie beim nächsten Lauf automatisch wieder dran. Die kleine Function FolgeStartAuto gibt es nur, weil die Makroaktion “AusführenCode” keine Subs aufrufen kann.
Der Kern: Postfächer, Ordner, Mails
Der Kern des Moduls geht in drei Ebenen vor. Er durchläuft alle Postfächer, darin rekursiv alle Ordner und darin jede einzelne Mail.
Jede Ebene hat ihre eigene Fehlerbehandlung. Eine defekte Mail stoppt nicht den Ordner, ein gesperrter Ordner nicht das Postfach und ein nicht erreichbares Online-Archiv nicht den ganzen Lauf. Nur wenn der Zielordner fehlt oder Outlook nicht antwortet, bricht der Lauf sofort ab, mit Status “Abgebrochen” und Klartext im Protokoll.
Bei Absendern aus dem eigenen Exchange liefert Outlook statt der Mailadresse einen internen Verzeichnisnamen. Die echte SMTP-Adresse holt der Code deshalb über den PropertyAccessor. Die Zeichen ‹ und › ersetzen die spitzen Klammern, die in Windows-Dateinamen verboten sind.
Protokoll und Hilfsfunktionen
Protokoll und Fehlerbehandlung laufen über wenige kleine Prozeduren.
FehlerLog schreibt zu jedem Fehler Postfach, Ordnerpfad, Betreff und Zieldatei mit. So findest du die betroffene Mail in Outlook gezielt wieder. Nach 200 Fehlern setzt die Prozedur ein Abbruchsignal, damit eine volle Festplatte keine zehntausend identischen Einträge erzeugt.
Die Hilfsfunktionen räumen Dateinamen auf, legen Ordner an und prüfen das Datenmodell.
Für Dateien und Ordner nutzt der Code das FileSystemObject statt der VBA-Befehle Dir und MkDir. Die alten Befehle scheitern an Unicode-Zeichen, und Betreffzeilen mit Emojis gibt es inzwischen in jedem Postfach.
Fehler des letzten Laufs anzeigen
Diese Abfrage zeigt alle Fehler des letzten Laufs:
SELECT f.Zeitpunkt, f.Prozedur, f.FehlerNr, f.FehlerText, f.Store, f.Ordner, f.Betreff, f.Datei
FROM tblBackupFehler AS f
WHERE f.LaufID = (SELECT Max(LaufID) FROM tblBackupLauf)
ORDER BY f.FehlerID;
Ein Blick in diese Abfrage nach dem wöchentlichen Lauf reicht. Bleibt sie leer, ist alles gesichert.
Wöchentlicher Lauf über die Aufgabenplanung
Für den wöchentlichen Lauf legst du in Access ein Makro mcrMailBackup an. Es enthält die Aktion “AusführenCode” mit FolgeStartAuto() und danach “Beenden”. Die Windows-Aufgabenplanung startet dann diese Befehlszeile:
“C:\Program Files\Microsoft Office\root\Office16\MSACCESS.EXE” “D:\Tools\MailBackup.accdb” /x mcrMailBackup
Die Aufgabe muss mit der Option “Nur ausführen, wenn der Benutzer angemeldet ist” laufen. Outlook braucht das angemeldete Benutzerprofil, sonst gibt es keinen Zugriff auf die Postfächer.
Der Pfad zu MSACCESS.EXE hängt von deiner Office-Installation ab. Bei 32-Bit-Office liegt er unter “Program Files (x86)”.
Wenn dich nicht der Code interessiert, sondern die Frage, welche Sicherung für die Größe deines Betriebs wirtschaftlich passt: Die Anbieter von Hornetsecurity bis Veeam habe ich in Microsoft sichert deine M365-Daten nicht verglichen.
Und jetzt?
Wenn du eine solche Sicherung für eure Postfächer eingerichtet haben möchtest, oder erst einmal wissen willst, wo eure Microsoft-365-Daten heute überhaupt überall liegen: Ein Gespräch dazu kannst du über sesoft.de/kostenloses-erstgespraech-sichern vereinbaren.
Quellen
- Dr. Windows: Outlook, Microsoft 365 und weitere Dienste erholen sich nach stundenlangem Ausfall (12./13.11.2026): https://www.drwindows.de/news/outlook-microsoft-365-und-weitere-dienste-derzeit-offenbar-massiv-gestoert
- ORF: Weltweite Ausfälle bei Microsoft-Diensten (13.11.2026): https://orf.at/stories/3448057/
- ZDFheute: Weltweite Outlook-Störung, Microsoft meldet Fortschritte (13.11.2026): https://www.zdfheute.de/wirtschaft/stoerung-outlook-teams-microsoft-100.html
- Microsoft-Servicevertrag: https://www.microsoft.com/de-de/servicesagreement
- Microsoft Learn: Preismodell für Microsoft 365 Backup: https://learn.microsoft.com/de-de/microsoft-365/backup/backup-pricing
- Microsoft: Microsoft 365-Backup (Produktseite): https://www.microsoft.com/de-at/microsoft-365/microsoft-365-backup
- Microsoft Learn: Geteilte Verantwortung in der Cloud: https://learn.microsoft.com/de-de/azure/security/fundamentals/shared-responsibility
Autor
Sönke Schäfer ist IT-Berater und Datenarchitekt aus Sierksdorf in Ostholstein. Er entwickelt seit über 25 Jahren Lösungen mit Microsoft Access, VBA und SQL Server für norddeutsche KMU, vor allem rund um gewachsene Bestandssysteme. Als ehemaliger IT-Leiter eines Unternehmens mit rund 300 Mitarbeitenden kennt er Backup-Fragen auch von der Seite, die nachts angerufen wird.


