Lets start by saying that if you are running a serious mail operation this is not what you are looking for. There are plenty of commercial suppliers who do a much more serious and functional product, and many of them do a free service for amateur users.
So why did I bother? Well, all the products I found were dependant on setting the rua record (describing the detail of DMARC is outside my scope here!) to their system, so that the DMARC reports go to their server and are processed by them. This is undoubtedly the way to go for anyone handling a serious amount of mail. But I only deal with tens of messages per month, and didn't want to bother with that!
So how does this work? Start by setting up the RUA record to point at a mailbox you have access to. The reports will come as messages, probably mainly from Google and MS, with attachments. The attachments may be zipped or gzipped. Download them to a folder and unzip them and you will get XML files with remarkably long file names like enterprise.protection.outlook.com![your domain]!1785801600!1785888000.xml Its not as cryptic as it looks, the numbers on the end are just unix timestamps. OK, now you have a folder full of xml files. Fire up word, run the macro PxmlOpenDocuments, you'll get a dialogue box where you can pick the files, select them and press OK. What you'll get is a word document with a very simplified subset of the data in tab delimited format. Look at in word, copy and paste to excel, whatever you like. Hopefully the vast majority will be passes, and you'll spot the odd fail which you'll probably want to investigate. But that too is beyond my scope here. I don't actually know much about this stuff!
I must apologise for the code. This is particularly rough and ready stuff, poorly commented and very crude. Its been a few years since I did much vba programming and boy was I rusty! I stole a few bits of example code but at least I didn't use AI. Its not very elegant. You could do better. Even *I* could do better, but I don't seem to need to. I spent about a day on it, should have been half that!
To use the macro suite paste the whole code section into an appropriate word template, either your main personal macro template, or else a custom one just for this. From there you could edit the ribbon to add buttons for one or more macros, or you could simply run the macro manually..
I've presented this as a code secton rather than a download. I think its all translated properly for that presentation, but if you try it and find any problems please let me know.
'University of Illinois/NCSA Open Source License
'Copyright (c) 2026 Jim Champ
'All rights reserved.
'Jim's DMARC parser
'Developed by: Jim Champ
'Permission is hereby granted, free of charge, to any person obtaining a
'copy of this software and associated documentation files (the "Software"),
'to deal with the Software without restriction, including without
'limitation the rights to use, copy, modify, merge, publish, distribute,
'sublicense, and/or sell copies of the Software, and to permit persons to
'whom the Software is furnished to do so, subject to the following
'conditions:
' * Redistributions of source code must retain the above copyright
' notice, this list of conditions and the following disclaimers.
' * Redistributions in binary form must reproduce the above copyright
' notice, this list of conditions and the following disclaimers in the
' documentation and/or other materials provided with the distribution.
' * Neither the names of Jim Champ,
' nor the names of its contributors may be used to endorse or promote
' products derived from this Software without specific prior written
' permission.
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
' OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
' IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
' ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
' CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
' THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
Global XMLData$
Sub PxmlOpenDocuments()
PxmlNewDoc
Dim fd As FileDialog
Dim vrtSelectedItem As Variant
Dim doc As Document
' Create a file picker dialog box
Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.Title = "Select Word Documents"
.AllowMultiSelect = True
.Filters.Clear
.Filters.Add "DMARC Documents", "*.xml"
' Show the dialog box. If user clicks OK, proceed.
If .Show = -1 Then
For Each vrtSelectedItem In .SelectedItems
'convery file to crlf (Google dcims are Unix formet)
PxmlConvertLFtoCR (vrtSelectedItem)
PxmlProcess (vrtSelectedItem)
Debug.Print vrtSelectedItem
Next vrtSelectedItem
End If
End With
Set fd = Nothing
End Sub
Private Sub PxmlConvertLFtoCR(fileToConvert As String)
' I grabbed this from a post by a "Easy-XL Support" on the Mr-Excel forum
'https://www.mrexcel.com/board/threads/how-to-convert-unix-text-file-to-pc-text-file.485534/page-2
'I altered it a bit so all line ends end up as crlf
' many thanks to original author
On Error GoTo ConvertFileLFtoCR_Error
' get next available VB file handle
Dim fileNum As Integer
fileNum = FreeFile
' open the file (read/write access required)
Open fileToConvert For Binary As #fileNum
' read the file into a string variable
Dim fileBuffer As String
fileBuffer = String(LOF(fileNum), 0)
Get #fileNum, , fileBuffer
' change all crlf to carriage returns
fileBuffer = Replace(fileBuffer, vbCrLf, vbCr)
' change all line feeds to carriage returns
fileBuffer = Replace(fileBuffer, vbLf, vbCr)
' change all cr to crlf
fileBuffer = Replace(fileBuffer, vbCr, vbCrLf)
' write the file back to disk
Put #fileNum, 1, fileBuffer
' close the file
Close #fileNum
' Thank you. Please come again.
Exit Sub
ConvertFileLFtoCR_Error:
MsgBox Err.Description
End Sub
Private Sub PxmlProcess(TheFile)
Dim OrgName$
Dim dkim_Status As Boolean
Dim spf_Status As Boolean
dkim_Status = False
spf_Status = False
Dim dkim_Domain$
Dim dkim_Result$
Dim spf_Domain$
Dim spf_Result$
fnum = FreeFile()
Open TheFile For Input As #fnum
'This will need to be a loop but...
While Not EOF(fnum)
'find ""
Line Input #fnum, XMLData$
Debug.Print XMLData$
If PxmlFind(XMLData$, "org_name") Then
Org_Name$ = PxmlStrip(XMLData$)
End If
If XMLData$ Like "**" Then
End_Date$ = PxmlStrip(XMLData$)
End If
If PxmlFind(XMLData$, "source_ip") Then
Src_IP$ = PxmlStrip(XMLData$)
End If
If PxmlFind(XMLData$, "envelope_to") Then
env_To$ = PxmlStrip(XMLData$)
End If
If PxmlFind(XMLData$, "envelope_from") Then
env_From$ = PxmlStrip(XMLData$)
End If
If PxmlFind(XMLData$, "header_from") Then
hdr_From$ = PxmlStrip(XMLData$)
End If
If dkim_Status = True Then
If PxmlFind(XMLData$, "domain") Then
dkim_Domain$ = PxmlStrip(XMLData$)
End If
If PxmlFind(XMLData$, "result") Then
dkim_Result$ = PxmlStrip(XMLData$)
End If
End If
If spf_Status = True Then
If PxmlFind(XMLData$, "domain") Then
spf_Domain$ = PxmlStrip(XMLData$)
End If
If PxmlFind(XMLData$, "result") Then
spf_Result$ = PxmlStrip(XMLData$)
End If
End If
If XMLData$ Like "** *" Then
'we want to skip
Else
If PxmlFind(XMLData$, "dkim") Then
dkim_Status = True
End If
If PxmlFind(XMLData$, "/dkim") Then
dkim_Status = False
End If
End If
If XMLData$ Like "** *" Then
'we want to skip
Else
If PxmlFind(XMLData$, "spf") Then
spf_Status = True
End If
If PxmlFind(XMLData$, "/spf") Then
spf_Status = False
End If
End If
If PxmlFind(XMLData$, "/record") Then
'We have a new record
'print the output
Selection.TypeText Text:=Org_Name$
Selection.TypeText Text:=vbTab
'EndDate$ is in Unix 10char format
If Len(End_Date$) = 10 Then
Selection.TypeText Text:=DateAdd("s", CDbl(End_Date$), "1/1/1970 00:00:00")
End If
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=Src_IP$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=env_To$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=env_From$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=hdr_From$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=dkim_Domain$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=dkim_Result$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=spf_Domain$
Selection.TypeText Text:=vbTab
Selection.TypeText Text:=spf_Result$
Selection.TypeText Text:=vbCrLf
'blank all vars except org name
Src_IP$ = ""
env_To$ = ""
env_From$ = ""
hdr_From$ = ""
dkim_Domain$ = ""
dkim_Result$ = ""
spf_Domain$ = ""
spf_Result$ = ""
End If
Wend
Close
End Sub
Private Sub PxmlNewDoc()
'create a word file for the output
Documents.Add Template:="Normal", NewTemplate:=False, DocumentType:=0
If Selection.PageSetup.Orientation = wdOrientPortrait Then
Selection.PageSetup.Orientation = wdOrientLandscape
End If
'and write the column headers
Selection.TypeText Text:="Org_Name"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="Date"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="Src_IP"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="Env_To"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="Env_From"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="Hdr_From"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="dkim_Domain"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="dkim_Result"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="spf_Domain"
Selection.TypeText Text:=vbTab
Selection.TypeText Text:="spf_Result"
Selection.TypeText Text:=vbCrLf
End Sub
Private Function PxmlFind(Aline As String, Ssearch As String)
If Aline Like "*<" & Ssearch & "*" Then
PxmlFind = True
Else
PxmlFind = False
End If
End Function
Private Function PxmlStrip(Aline As String)
If Aline Like "*<*>**>" Then
sttpos = InStr(Aline, "<")
Aline = Mid(Aline, sttpos + 1)
'strips off all up to first
endpos = InStr(Aline, ">")
Aline = Mid(Aline, endpos + 1)
'strips off first tag
nextpos = InStr(Aline, "<")
Aline = Left(Aline, nextpos - 1)
PxmlStrip = Aline
Else
Aline = vbNull
End If
End Function
These snippets and utilities are licensed under the University of Illinois/NCSA Open Source License. Here is the text of the license as it applies to this code.