diff --git a/.gitignore b/.gitignore
index e4b215347bd5452cbce73a4bd9397f065745b9a6..de0fc89a3d0577749cdba4cfe211fb15e7db3971 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,7 @@ dmarc-prometheus-exporter
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
+
+# Prometheus report files
+*.prom
+reports
diff --git a/README.md b/README.md
index 0ef97482ad8c24879327e2e07bf3cd4c9623906e..6c6a88311e98cea5654569086d73627c591cf2ef 100644
--- a/README.md
+++ b/README.md
@@ -16,13 +16,14 @@ You can download one of the pre-compiled binaries from the list below.
 
 ## Configuration
 
-To avoid providing the MQTT URL via a flag every time, you can define an environment variable named `MQTT_URL` instead. The MQTT topic can also be set via the environment variable `MQTT_TOPIC`.
+To avoid providing the MQTT URL via a flag every time, you can define an environment variable named `MQTT_URL` instead. The MQTT topics can also be set via the environment variables `MQTT_TOPIC_RUA` and `MQTT_TOPIC_RUF`.
 
 If you prefer you can also set these values in a `.env` file like this:
 
 ```env
 MQTT_URL=mqtts://[username]:[password]@mqtt.example.com:8883
-MQTT_TOPIC=gcf/email/dmarc@example.com
+MQTT_TOPIC_RUA=gcf/email/dmarc-rua@example.com
+MQTT_TOPIC_RUF=gcf/email/dmarc-ruf@example.com
 ```
 
 ## Usage
@@ -32,3 +33,14 @@ MQTT_TOPIC=gcf/email/dmarc@example.com
 ```
 
 See the [generated documentation](https://gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/-/blob/main/docs/dmarc-prometheus-exporter.md) for more information about the various commands.
+
+## Testing
+
+```bash
+> curl -F 'data=@test/google.com!hedenstroem.com!1644364800!1644451199.zip' http://localhost:9100/rua
+```
+
+
+```bash
+> curl -X POST -H "Content-Type: application/json" -d @test/rua_email.json https://example.com/emailToMQTT
+```
diff --git a/cmd/analyze.go b/cmd/analyze.go
new file mode 100644
index 0000000000000000000000000000000000000000..7b720f691cd77336827d45131cac571f963205ea
--- /dev/null
+++ b/cmd/analyze.go
@@ -0,0 +1,49 @@
+package cmd
+
+import (
+	"errors"
+	"io/ioutil"
+	"os"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/spf13/cobra"
+	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/utils"
+)
+
+var reportPath string
+
+var analyzeCmd = &cobra.Command{
+	Use:   "analyze",
+	Short: "Analyze DMARC reports",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		if len(args) == 0 {
+			return errors.New("no report file specified")
+		}
+		for _, arg := range args {
+			file, err := os.Open(arg)
+			if err != nil {
+				log.Warnf("Open %s: %s", file, err)
+				continue
+			}
+			defer file.Close()
+			data, err := ioutil.ReadAll(file)
+			if err != nil {
+				log.Warnf("Read %s: %s", file, err)
+				continue
+			}
+			r, err := utils.Parse(data)
+			if err != nil {
+				log.Warnf("Parse %s: %s", file, err)
+				continue
+			}
+			utils.Analyze(r)
+		}
+		return prometheus.WriteToTextfile(reportPath, prometheus.DefaultGatherer)
+	},
+	DisableAutoGenTag: true,
+}
+
+func init() {
+	RootCmd.AddCommand(analyzeCmd)
+	analyzeCmd.Flags().StringVarP(&reportPath, "output", "o", "./report.prom", "Report file")
+}
diff --git a/cmd/root.go b/cmd/root.go
index 8251259db3be70edc4b4bbf4611284f0d355db8f..08b3d5cda85d8adc0302ab56acf92867200ce2bf 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -4,18 +4,26 @@ import (
 	"os"
 
 	"github.com/joho/godotenv"
+	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 )
 
 var (
 	debug bool
+	log   *logrus.Logger
 )
 
 var RootCmd = &cobra.Command{
 	Use:               "dmarc-prometheus-exporter",
 	Short:             "Export metrics derived from DMARC aggregate reports",
 	DisableAutoGenTag: true,
+	PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		log = logrus.New()
+		if debug {
+			log.SetLevel(logrus.DebugLevel)
+		}
+	},
 }
 
 func Execute() {
diff --git a/cmd/run.go b/cmd/run.go
new file mode 100644
index 0000000000000000000000000000000000000000..66a48ccef3ef04889c148cc79119332b1cf97d7b
--- /dev/null
+++ b/cmd/run.go
@@ -0,0 +1,242 @@
+package cmd
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	mqtt "github.com/eclipse/paho.mqtt.golang"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"github.com/prometheus/common/expfmt"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types"
+	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/utils"
+)
+
+var runCmd = &cobra.Command{
+	Use:   "run",
+	Short: "Process report emails from MQTT topic or via HTTP uploads",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		opts, err := createClientOptions()
+		if err != nil {
+			return err
+		}
+		client := mqtt.NewClient(opts)
+		if token := client.Connect(); token.Wait() && token.Error() != nil {
+			return token.Error()
+		}
+		client.Subscribe(viper.GetString("MQTT_TOPIC_RUA"), 0, ruaMQTTHandler)
+		client.Subscribe(viper.GetString("MQTT_TOPIC_RUF"), 0, rufMQTTHandler)
+		return <-startHTTPServer()
+	},
+	PreRunE: func(cmd *cobra.Command, args []string) (err error) {
+		if viper.GetString("BACKUP_FILE") != "" {
+			log.Infof("Restoring state from %s", viper.GetString("BACKUP_FILE"))
+			reader, err := os.Open(viper.GetString("BACKUP_FILE"))
+			if err == nil {
+				defer reader.Close()
+				var parser expfmt.TextParser
+				mf, err := parser.TextToMetricFamilies(reader)
+				if err == nil {
+					utils.Restore(mf)
+				} else {
+					log.Warnf("Error parsing %s: %s", viper.GetString("BACKUP_FILE"), err)
+				}
+			} else {
+				log.Warnf("Error opening %s: %s", viper.GetString("BACKUP_FILE"), err)
+			}
+		}
+		return
+	},
+	PostRunE: func(cmd *cobra.Command, args []string) (err error) {
+		if viper.GetString("BACKUP_FILE") != "" {
+			log.Infof("Backing up state to %s", viper.GetString("BACKUP_FILE"))
+			err = prometheus.WriteToTextfile(viper.GetString("BACKUP_FILE"), prometheus.DefaultGatherer)
+			if err != nil {
+				log.Warnf("Backup error: %s", err)
+			}
+		}
+		return
+	},
+	DisableAutoGenTag: true,
+}
+
+func startHTTPServer() chan error {
+
+	errC := make(chan error, 1)
+
+	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
+
+	mux := http.NewServeMux()
+	mux.Handle("/metrics", promhttp.Handler())
+	mux.HandleFunc("/rua", ruaHTTPHandler)
+	mux.HandleFunc("/ruf", rufHTTPHandler)
+
+	srv := &http.Server{
+		Addr:    viper.GetString("HTTP_ADDR"),
+		Handler: mux,
+	}
+
+	go func() {
+		<-ctx.Done()
+		log.Debug("Shutdown signal received")
+		ctxTimeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		defer func() {
+			stop()
+			cancel()
+			close(errC)
+		}()
+		srv.SetKeepAlivesEnabled(false)
+		if err := srv.Shutdown(ctxTimeout); err != nil {
+			errC <- err
+		}
+		log.Info("Shutdown completed")
+	}()
+
+	go func() {
+		log.Infof("Listening and serving at %s", srv.Addr)
+		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+			errC <- err
+		}
+	}()
+
+	return errC
+}
+
+func ruaHTTPHandler(w http.ResponseWriter, r *http.Request) {
+	if err := r.ParseMultipartForm(10 << 20); err != nil {
+		log.Warn(err)
+		return
+	}
+	for _, fileHeaders := range r.MultipartForm.File {
+		for _, fileHeader := range fileHeaders {
+			file, _ := fileHeader.Open()
+			defer file.Close()
+			body, err := ioutil.ReadAll(file)
+			if err != nil {
+				log.Warn(err)
+				continue
+			}
+			log.Debugf("Parsing file upload %s", fileHeader.Filename)
+			r, err := utils.Parse(body)
+			if err != nil {
+				log.Warn(err)
+				continue
+			}
+			log.Debugf("Analyzing file upload %s", fileHeader.Filename)
+			utils.Analyze(r)
+		}
+	}
+}
+
+func rufHTTPHandler(w http.ResponseWriter, r *http.Request) {
+	if err := r.ParseMultipartForm(10 << 20); err != nil {
+		log.Warn(err)
+		return
+	}
+	for _, fileHeaders := range r.MultipartForm.File {
+		for _, fileHeader := range fileHeaders {
+			log.Debugf("Storing file upload %s", fileHeader.Filename)
+			src, _ := fileHeader.Open()
+			defer src.Close()
+			dst, err := os.Create(fmt.Sprintf("%s/%s", viper.GetString("OUTPUT_PATH"), fileHeader.Filename))
+			if err != nil {
+				log.Warn(err)
+				continue
+			}
+			defer dst.Close()
+			io.Copy(dst, src)
+		}
+	}
+}
+
+func ruaMQTTHandler(client mqtt.Client, msg mqtt.Message) {
+	var email types.Email
+	if err := json.Unmarshal(msg.Payload(), &email); err != nil {
+		log.Warn(err)
+		return
+	}
+	for _, attachment := range email.Attachments {
+		log.Debugf("Parsing email attachment %s", attachment.Filename)
+		r, err := utils.Parse(attachment.Content.Data)
+		if err != nil {
+			log.Warn(err)
+			continue
+		}
+		log.Debugf("Analyzing email attachment %s", attachment.Filename)
+		utils.Analyze(r)
+	}
+}
+
+func rufMQTTHandler(client mqtt.Client, msg mqtt.Message) {
+	var email types.Email
+	if err := json.Unmarshal(msg.Payload(), &email); err != nil {
+		log.Warn(err)
+		return
+	}
+	for _, attachment := range email.Attachments {
+		log.Debugf("Storing email attachment %s", attachment.Filename)
+		dst, err := os.Create(fmt.Sprintf("%s/%s", viper.GetString("OUTPUT_PATH"), attachment.Filename))
+		if err != nil {
+			log.Warn(err)
+			continue
+		}
+		defer dst.Close()
+		_, err = dst.Write(attachment.Content.Data)
+		if err != nil {
+			log.Warn(err)
+		}
+	}
+}
+
+func createClientOptions() (*mqtt.ClientOptions, error) {
+	uri, err := url.Parse(viper.GetString("MQTT_URL"))
+	if err != nil {
+		return nil, err
+	}
+	var server string
+	switch uri.Scheme {
+	case "mqtts":
+		server = fmt.Sprintf("ssl://%s", uri.Host)
+	case "ws":
+		server = fmt.Sprintf("ws://%s", uri.Host)
+	default:
+		server = fmt.Sprintf("tcp://%s", uri.Host)
+	}
+	opts := mqtt.NewClientOptions()
+	opts.AddBroker(server)
+	if password, passwordSet := uri.User.Password(); passwordSet {
+		opts.SetUsername(uri.User.Username())
+		opts.SetPassword(password)
+	}
+	opts.SetClientID(viper.GetString("MQTT_CLIENT_ID"))
+	return opts, nil
+}
+
+func init() {
+	runCmd.Flags().StringP("url", "u", "tcp://127.0.0.1:1883", "MQTT Broker URL")
+	runCmd.Flags().StringP("topic_rua", "t", "", "MQTT Aggregate Report Topic")
+	runCmd.Flags().StringP("topic_ruf", "f", "", "MQTT Forensic Report Topic")
+	runCmd.Flags().StringP("id", "i", "dmarc-prometheus-exporter", "MQTT Client ID")
+	runCmd.Flags().StringP("addr", "a", ":9100", "TCP network address to listen on")
+	runCmd.Flags().StringP("output", "o", "reports", "Forensic reports storage path")
+	runCmd.Flags().StringP("backup", "b", "", "Metrics backup")
+	viper.BindPFlag("MQTT_URL", runCmd.Flags().Lookup("url"))
+	viper.BindPFlag("MQTT_TOPIC_RUA", runCmd.Flags().Lookup("topic_rua"))
+	viper.BindPFlag("MQTT_TOPIC_RUF", runCmd.Flags().Lookup("topic_ruf"))
+	viper.BindPFlag("MQTT_CLIENT_ID", runCmd.Flags().Lookup("id"))
+	viper.BindPFlag("HTTP_ADDR", runCmd.Flags().Lookup("addr"))
+	viper.BindPFlag("OUTPUT_PATH", runCmd.Flags().Lookup("output"))
+	viper.BindPFlag("BACKUP_FILE", runCmd.Flags().Lookup("backup"))
+	RootCmd.AddCommand(runCmd)
+}
diff --git a/cmd/subscribe.go b/cmd/subscribe.go
deleted file mode 100644
index 3fa6ed2af0f5e2d7b26d9c044d85afc5dc19c97f..0000000000000000000000000000000000000000
--- a/cmd/subscribe.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package cmd
-
-import (
-	"encoding/json"
-	"encoding/xml"
-	"fmt"
-	"net/url"
-	"os"
-	"os/signal"
-	"syscall"
-
-	"github.com/davecgh/go-spew/spew"
-	mqtt "github.com/eclipse/paho.mqtt.golang"
-	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
-	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types"
-	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/utils"
-)
-
-var subscribeCmd = &cobra.Command{
-	Use:   "subscribe",
-	Short: "Subscribe to emails on an MQTT topic",
-	RunE: func(cmd *cobra.Command, args []string) error {
-
-		opts, err := createClientOptions()
-		if err != nil {
-			return err
-		}
-		client := mqtt.NewClient(opts)
-		if token := client.Connect(); token.Wait() && token.Error() != nil {
-			return token.Error()
-		}
-		client.Subscribe(viper.GetString("MQTT_TOPIC"), 0, callback)
-
-		keepAlive := make(chan os.Signal)
-		signal.Notify(keepAlive, os.Interrupt, syscall.SIGTERM)
-		<-keepAlive
-
-		return nil
-	},
-	DisableAutoGenTag: true,
-}
-
-func callback(client mqtt.Client, msg mqtt.Message) {
-	var email types.Email
-	if err := json.Unmarshal(msg.Payload(), &email); err == nil {
-		for _, attachment := range email.Attachments {
-			if attachment.ContentType == "application/zip" {
-				files, _ := utils.UnzipBytes(attachment.Content.Data)
-				for _, file := range files {
-					if file.HasSuffix(".xml") {
-						var feedback types.Feedback
-						feedback.FromFile = file.Name
-						if err := xml.Unmarshal(file.Data, &feedback); err == nil {
-							spew.Dump(feedback)
-						}
-					}
-				}
-			}
-		}
-	}
-}
-
-func createClientOptions() (*mqtt.ClientOptions, error) {
-	uri, err := url.Parse(viper.GetString("MQTT_URL"))
-	if err != nil {
-		return nil, err
-	}
-	var server string
-	switch uri.Scheme {
-	case "mqtts":
-		server = fmt.Sprintf("ssl://%s", uri.Host)
-	case "ws":
-		server = fmt.Sprintf("ws://%s", uri.Host)
-	default:
-		server = fmt.Sprintf("tcp://%s", uri.Host)
-	}
-	opts := mqtt.NewClientOptions()
-	opts.AddBroker(server)
-	if password, passwordSet := uri.User.Password(); passwordSet {
-		opts.SetUsername(uri.User.Username())
-		opts.SetPassword(password)
-	}
-	opts.SetClientID(viper.GetString("MQTT_CLIENT_ID"))
-	return opts, nil
-}
-
-func init() {
-	subscribeCmd.Flags().StringP("url", "u", "tcp://127.0.0.1:1883", "MQTT Broker URL")
-	subscribeCmd.Flags().StringP("topic", "t", "", "MQTT Topic")
-	subscribeCmd.Flags().StringP("id", "i", "dmarc-prometheus-exporter", "MQTT Client ID")
-	viper.BindPFlag("MQTT_URL", subscribeCmd.Flags().Lookup("url"))
-	viper.BindPFlag("MQTT_TOPIC", subscribeCmd.Flags().Lookup("topic"))
-	viper.BindPFlag("MQTT_CLIENT_ID", subscribeCmd.Flags().Lookup("id"))
-	RootCmd.AddCommand(subscribeCmd)
-}
diff --git a/docs/dmarc-prometheus-exporter.md b/docs/dmarc-prometheus-exporter.md
index e9b542cd19e273ac9bf546d6f32e7f4bd6b8553e..6ec0b04cba180bb0af172cf531c95fc0b3f809fc 100644
--- a/docs/dmarc-prometheus-exporter.md
+++ b/docs/dmarc-prometheus-exporter.md
@@ -11,6 +11,8 @@ Export metrics derived from DMARC aggregate reports
 
 ### SEE ALSO
 
+* [dmarc-prometheus-exporter analyze](dmarc-prometheus-exporter_analyze.md)	 - Analyze DMARC reports
 * [dmarc-prometheus-exporter completion](dmarc-prometheus-exporter_completion.md)	 - Generate the autocompletion script for the specified shell
+* [dmarc-prometheus-exporter run](dmarc-prometheus-exporter_run.md)	 - Process report emails from MQTT topic or via HTTP uploads
 * [dmarc-prometheus-exporter version](dmarc-prometheus-exporter_version.md)	 - Display udm-query version
 
diff --git a/docs/dmarc-prometheus-exporter_analyze.md b/docs/dmarc-prometheus-exporter_analyze.md
new file mode 100644
index 0000000000000000000000000000000000000000..909eb8fdf02d613d5f28fdb0847ed467d63bec89
--- /dev/null
+++ b/docs/dmarc-prometheus-exporter_analyze.md
@@ -0,0 +1,25 @@
+## dmarc-prometheus-exporter analyze
+
+Analyze DMARC reports
+
+```
+dmarc-prometheus-exporter analyze [flags]
+```
+
+### Options
+
+```
+  -h, --help            help for analyze
+  -o, --output string   Report file (default "./report.prom")
+```
+
+### Options inherited from parent commands
+
+```
+      --debug   Show debug output
+```
+
+### SEE ALSO
+
+* [dmarc-prometheus-exporter](dmarc-prometheus-exporter.md)	 - Export metrics derived from DMARC aggregate reports
+
diff --git a/docs/dmarc-prometheus-exporter_run.md b/docs/dmarc-prometheus-exporter_run.md
new file mode 100644
index 0000000000000000000000000000000000000000..d5345de9657455a46edb50550f2ed8f0fb79a7a6
--- /dev/null
+++ b/docs/dmarc-prometheus-exporter_run.md
@@ -0,0 +1,31 @@
+## dmarc-prometheus-exporter run
+
+Process report emails from MQTT topic or via HTTP uploads
+
+```
+dmarc-prometheus-exporter run [flags]
+```
+
+### Options
+
+```
+  -a, --addr string        TCP network address to listen on (default ":9100")
+  -b, --backup string      Metrics backup
+  -h, --help               help for run
+  -i, --id string          MQTT Client ID (default "dmarc-prometheus-exporter")
+  -o, --output string      Forensic reports storage path (default "reports")
+  -t, --topic_rua string   MQTT Aggregate Report Topic
+  -f, --topic_ruf string   MQTT Forensic Report Topic
+  -u, --url string         MQTT Broker URL (default "tcp://127.0.0.1:1883")
+```
+
+### Options inherited from parent commands
+
+```
+      --debug   Show debug output
+```
+
+### SEE ALSO
+
+* [dmarc-prometheus-exporter](dmarc-prometheus-exporter.md)	 - Export metrics derived from DMARC aggregate reports
+
diff --git a/go.mod b/go.mod
index 66e0bd7cdc1bb54af7e3fa942150c53e8b23b2e4..b5d432e9c6c5aa876d71b000c5f19b31f006ec88 100644
--- a/go.mod
+++ b/go.mod
@@ -3,22 +3,33 @@ module gitlab.hedenstroem.com/go/dmarc-prometheus-exporter
 go 1.17
 
 require (
-	github.com/davecgh/go-spew v1.1.1
 	github.com/eclipse/paho.mqtt.golang v1.3.5
 	github.com/joho/godotenv v1.4.0
+	github.com/prometheus/client_golang v1.4.0
+	github.com/prometheus/client_model v0.2.0
+	github.com/prometheus/common v0.9.1
+	github.com/sirupsen/logrus v1.4.2
 	github.com/spf13/cobra v1.3.0
 	github.com/spf13/viper v1.10.1
 )
 
 require (
+	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
+	github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
 	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/mitchellh/mapstructure v1.4.3 // indirect
 	github.com/pelletier/go-toml v1.9.4 // indirect
+	github.com/prometheus/procfs v0.0.8 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/spf13/afero v1.6.0 // indirect
 	github.com/spf13/cast v1.4.1 // indirect
@@ -28,6 +39,8 @@ require (
 	golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
 	golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
 	golang.org/x/text v0.3.7 // indirect
+	google.golang.org/protobuf v1.27.1 // indirect
+	gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
 	gopkg.in/ini.v1 v1.66.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 77ea11eeeb70d33f786bf8f974524f8fe075dd63..3430c45d2df2c6bbde487637779a47ad2a5f8168 100644
--- a/go.sum
+++ b/go.sum
@@ -51,8 +51,10 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -62,12 +64,15 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -152,6 +157,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -167,6 +173,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -244,6 +251,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -267,6 +275,7 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@@ -297,15 +306,19 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
 github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0 h1:YVIb/fVcOTMSqtqZWSKnHpSLBxu8DKgxq8z6RuBZwqI=
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -316,6 +329,7 @@ github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43
 github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
@@ -618,6 +632,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -763,7 +778,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/test/google.com!kornfehl.se!1644364800!1644451199.xml.gz b/test/google.com!kornfehl.se!1644364800!1644451199.xml.gz
new file mode 100644
index 0000000000000000000000000000000000000000..4992e9df3be6869506fa8fd88f64e8ac099242be
Binary files /dev/null and b/test/google.com!kornfehl.se!1644364800!1644451199.xml.gz differ
diff --git a/test/rua_email.json b/test/rua_email.json
new file mode 100644
index 0000000000000000000000000000000000000000..f96756f171cd2b25e778dcd4bceda5c5a17d9148
--- /dev/null
+++ b/test/rua_email.json
@@ -0,0 +1,907 @@
+{
+    "attachments": [
+        {
+            "type": "attachment",
+            "content": {
+                "type": "Buffer",
+                "data": [
+                    80,
+                    75,
+                    3,
+                    4,
+                    10,
+                    0,
+                    0,
+                    0,
+                    8,
+                    0,
+                    64,
+                    84,
+                    87,
+                    84,
+                    206,
+                    210,
+                    135,
+                    28,
+                    85,
+                    2,
+                    0,
+                    0,
+                    134,
+                    22,
+                    0,
+                    0,
+                    52,
+                    0,
+                    0,
+                    0,
+                    103,
+                    111,
+                    111,
+                    103,
+                    108,
+                    101,
+                    46,
+                    99,
+                    111,
+                    109,
+                    33,
+                    104,
+                    101,
+                    100,
+                    101,
+                    110,
+                    115,
+                    116,
+                    114,
+                    111,
+                    101,
+                    109,
+                    46,
+                    99,
+                    111,
+                    109,
+                    33,
+                    49,
+                    54,
+                    52,
+                    53,
+                    52,
+                    56,
+                    56,
+                    48,
+                    48,
+                    48,
+                    33,
+                    49,
+                    54,
+                    52,
+                    53,
+                    53,
+                    55,
+                    52,
+                    51,
+                    57,
+                    57,
+                    46,
+                    120,
+                    109,
+                    108,
+                    237,
+                    88,
+                    203,
+                    142,
+                    163,
+                    48,
+                    16,
+                    188,
+                    207,
+                    87,
+                    68,
+                    185,
+                    199,
+                    224,
+                    240,
+                    74,
+                    86,
+                    12,
+                    179,
+                    167,
+                    253,
+                    130,
+                    221,
+                    51,
+                    114,
+                    76,
+                    67,
+                    172,
+                    1,
+                    219,
+                    178,
+                    205,
+                    60,
+                    254,
+                    126,
+                    77,
+                    48,
+                    132,
+                    33,
+                    251,
+                    208,
+                    72,
+                    123,
+                    88,
+                    69,
+                    62,
+                    5,
+                    151,
+                    187,
+                    219,
+                    213,
+                    213,
+                    101,
+                    36,
+                    146,
+                    63,
+                    189,
+                    117,
+                    237,
+                    230,
+                    5,
+                    148,
+                    102,
+                    130,
+                    63,
+                    110,
+                    49,
+                    10,
+                    183,
+                    27,
+                    224,
+                    84,
+                    84,
+                    140,
+                    55,
+                    143,
+                    219,
+                    31,
+                    223,
+                    191,
+                    237,
+                    14,
+                    219,
+                    205,
+                    83,
+                    241,
+                    144,
+                    215,
+                    0,
+                    213,
+                    137,
+                    208,
+                    231,
+                    226,
+                    97,
+                    179,
+                    201,
+                    21,
+                    72,
+                    161,
+                    76,
+                    217,
+                    129,
+                    33,
+                    21,
+                    49,
+                    100,
+                    192,
+                    44,
+                    42,
+                    84,
+                    83,
+                    114,
+                    210,
+                    65,
+                    209,
+                    8,
+                    209,
+                    180,
+                    128,
+                    168,
+                    232,
+                    242,
+                    96,
+                    6,
+                    199,
+                    24,
+                    232,
+                    8,
+                    107,
+                    11,
+                    46,
+                    108,
+                    133,
+                    246,
+                    125,
+                    87,
+                    117,
+                    68,
+                    209,
+                    157,
+                    238,
+                    229,
+                    80,
+                    238,
+                    235,
+                    50,
+                    109,
+                    140,
+                    115,
+                    57,
+                    111,
+                    70,
+                    145,
+                    146,
+                    10,
+                    110,
+                    8,
+                    53,
+                    37,
+                    227,
+                    181,
+                    40,
+                    206,
+                    198,
+                    72,
+                    253,
+                    37,
+                    8,
+                    92,
+                    42,
+                    186,
+                    166,
+                    6,
+                    36,
+                    32,
+                    92,
+                    191,
+                    130,
+                    10,
+                    246,
+                    113,
+                    154,
+                    38,
+                    135,
+                    208,
+                    214,
+                    186,
+                    205,
+                    31,
+                    11,
+                    187,
+                    54,
+                    88,
+                    85,
+                    68,
+                    217,
+                    49,
+                    78,
+                    195,
+                    40,
+                    10,
+                    143,
+                    25,
+                    14,
+                    15,
+                    56,
+                    74,
+                    51,
+                    155,
+                    119,
+                    221,
+                    30,
+                    195,
+                    109,
+                    171,
+                    80,
+                    42,
+                    194,
+                    27,
+                    215,
+                    140,
+                    133,
+                    78,
+                    208,
+                    48,
+                    94,
+                    224,
+                    52,
+                    78,
+                    226,
+                    195,
+                    33,
+                    12,
+                    109,
+                    210,
+                    136,
+                    76,
+                    251,
+                    192,
+                    171,
+                    203,
+                    110,
+                    146,
+                    197,
+                    209,
+                    241,
+                    104,
+                    169,
+                    240,
+                    169,
+                    88,
+                    240,
+                    177,
+                    218,
+                    124,
+                    218,
+                    82,
+                    211,
+                    92,
+                    138,
+                    150,
+                    209,
+                    247,
+                    82,
+                    246,
+                    167,
+                    150,
+                    233,
+                    51,
+                    204,
+                    68,
+                    132,
+                    85,
+                    135,
+                    23,
+                    22,
+                    0,
+                    174,
+                    141,
+                    18,
+                    208,
+                    141,
+                    162,
+                    57,
+                    124,
+                    12,
+                    34,
+                    213,
+                    51,
+                    235,
+                    10,
+                    149,
+                    7,
+                    227,
+                    131,
+                    3,
+                    181,
+                    172,
+                    47,
+                    216,
+                    240,
+                    59,
+                    66,
+                    210,
+                    78,
+                    131,
+                    67,
+                    30,
+                    72,
+                    183,
+                    214,
+                    19,
+                    160,
+                    39,
+                    68,
+                    82,
+                    83,
+                    224,
+                    161,
+                    183,
+                    225,
+                    225,
+                    194,
+                    245,
+                    87,
+                    188,
+                    172,
+                    154,
+                    84,
+                    168,
+                    137,
+                    162,
+                    18,
+                    175,
+                    179,
+                    8,
+                    90,
+                    244,
+                    138,
+                    66,
+                    201,
+                    164,
+                    173,
+                    18,
+                    163,
+                    40,
+                    68,
+                    24,
+                    225,
+                    100,
+                    111,
+                    15,
+                    152,
+                    241,
+                    41,
+                    146,
+                    138,
+                    158,
+                    219,
+                    179,
+                    242,
+                    96,
+                    124,
+                    152,
+                    96,
+                    119,
+                    28,
+                    188,
+                    144,
+                    182,
+                    183,
+                    170,
+                    85,
+                    211,
+                    198,
+                    32,
+                    5,
+                    211,
+                    82,
+                    104,
+                    102,
+                    172,
+                    123,
+                    29,
+                    237,
+                    37,
+                    178,
+                    136,
+                    27,
+                    52,
+                    144,
+                    68,
+                    107,
+                    27,
+                    48,
+                    203,
+                    225,
+                    250,
+                    173,
+                    221,
+                    198,
+                    172,
+                    201,
+                    162,
+                    197,
+                    213,
+                    153,
+                    118,
+                    76,
+                    83,
+                    99,
+                    57,
+                    179,
+                    234,
+                    27,
+                    86,
+                    51,
+                    123,
+                    119,
+                    230,
+                    180,
+                    51,
+                    144,
+                    10,
+                    84,
+                    89,
+                    43,
+                    209,
+                    221,
+                    142,
+                    103,
+                    185,
+                    233,
+                    170,
+                    221,
+                    212,
+                    200,
+                    73,
+                    111,
+                    206,
+                    165,
+                    2,
+                    221,
+                    183,
+                    230,
+                    90,
+                    118,
+                    69,
+                    217,
+                    205,
+                    249,
+                    114,
+                    71,
+                    16,
+                    109,
+                    69,
+                    95,
+                    213,
+                    45,
+                    81,
+                    128,
+                    56,
+                    152,
+                    143,
+                    38,
+                    112,
+                    46,
+                    31,
+                    138,
+                    185,
+                    30,
+                    221,
+                    98,
+                    209,
+                    62,
+                    180,
+                    64,
+                    141,
+                    80,
+                    197,
+                    62,
+                    220,
+                    15,
+                    51,
+                    153,
+                    150,
+                    179,
+                    16,
+                    203,
+                    179,
+                    127,
+                    67,
+                    228,
+                    143,
+                    70,
+                    252,
+                    12,
+                    7,
+                    141,
+                    255,
+                    202,
+                    96,
+                    49,
+                    164,
+                    127,
+                    68,
+                    96,
+                    49,
+                    120,
+                    123,
+                    49,
+                    86,
+                    242,
+                    15,
+                    193,
+                    147,
+                    173,
+                    63,
+                    225,
+                    240,
+                    61,
+                    194,
+                    217,
+                    209,
+                    59,
+                    220,
+                    59,
+                    252,
+                    142,
+                    29,
+                    158,
+                    120,
+                    131,
+                    123,
+                    131,
+                    223,
+                    175,
+                    193,
+                    99,
+                    132,
+                    113,
+                    230,
+                    29,
+                    238,
+                    29,
+                    190,
+                    230,
+                    48,
+                    126,
+                    107,
+                    220,
+                    137,
+                    203,
+                    19,
+                    20,
+                    165,
+                    222,
+                    228,
+                    222,
+                    228,
+                    107,
+                    14,
+                    119,
+                    243,
+                    26,
+                    79,
+                    16,
+                    14,
+                    177,
+                    119,
+                    184,
+                    119,
+                    248,
+                    154,
+                    195,
+                    93,
+                    189,
+                    198,
+                    99,
+                    20,
+                    123,
+                    143,
+                    123,
+                    143,
+                    175,
+                    57,
+                    252,
+                    207,
+                    111,
+                    241,
+                    60,
+                    184,
+                    254,
+                    165,
+                    252,
+                    19,
+                    80,
+                    75,
+                    1,
+                    2,
+                    10,
+                    0,
+                    10,
+                    0,
+                    0,
+                    0,
+                    8,
+                    0,
+                    64,
+                    84,
+                    87,
+                    84,
+                    206,
+                    210,
+                    135,
+                    28,
+                    85,
+                    2,
+                    0,
+                    0,
+                    134,
+                    22,
+                    0,
+                    0,
+                    52,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    0,
+                    103,
+                    111,
+                    111,
+                    103,
+                    108,
+                    101,
+                    46,
+                    99,
+                    111,
+                    109,
+                    33,
+                    104,
+                    101,
+                    100,
+                    101,
+                    110,
+                    115,
+                    116,
+                    114,
+                    111,
+                    101,
+                    109,
+                    46,
+                    99,
+                    111,
+                    109,
+                    33,
+                    49,
+                    54,
+                    52,
+                    53,
+                    52,
+                    56,
+                    56,
+                    48,
+                    48,
+                    48,
+                    33,
+                    49,
+                    54,
+                    52,
+                    53,
+                    53,
+                    55,
+                    52,
+                    51,
+                    57,
+                    57,
+                    46,
+                    120,
+                    109,
+                    108,
+                    80,
+                    75,
+                    5,
+                    6,
+                    0,
+                    0,
+                    0,
+                    0,
+                    1,
+                    0,
+                    1,
+                    0,
+                    98,
+                    0,
+                    0,
+                    0,
+                    167,
+                    2,
+                    0,
+                    0,
+                    0,
+                    0
+                ]
+            },
+            "contentType": "application/zip",
+            "partId": null,
+            "release": null,
+            "contentDisposition": "attachment",
+            "filename": "google.com!hedenstroem.com!1645488000!1645574399.zip",
+            "headers": {},
+            "checksum": "ec791b24e94bd814161b16a875b889f5",
+            "size": 799
+        }
+    ],
+    "headers": {},
+    "headerLines": [
+        {
+            "key": "received",
+            "line": "Received: by mail-qk1-f202.google.com with SMTP id w4-20020a05620a094400b0060dd52a1445so2827056qkw.3\r\n        for <dmarc-rua@npc.hedenstroem.com>; Wed, 23 Feb 2022 02:56:28 -0800 (PST)"
+        },
+        {
+            "key": "dkim-signature",
+            "line": "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n        d=google.com; s=20210112;\r\n        h=mime-version:date:message-id:subject:from:to:content-disposition\r\n         :content-transfer-encoding;\r\n        bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n        b=nFD6sQTA/w8rNXXgj3LpFi1k1brhM1E/8v7nwa/DqfmJ6nH3+m7FSVVIiHR9LWc29Y\r\n         4FG8x6jzRBxssNoxNDRb8if2zT2GQq980NUFvZ5rKvNs2ltIoq5UntrJwhxG5OVhWRdy\r\n         8wJN3p9625htJgZnHobWNLengIV/byE328+FQ5tzmBj4dIPZy+BvifXVd8W4dJOevWWi\r\n         cHc8ZLbLfH+5v30HrBMt3EmOsBoZ1ZKBNIVEil4FRPm33K8MhsF/TMO4PQVpwCOYeiI6\r\n         R4SJdQ7MdIh00v901V8VAlwxPVzdfauEEP7nReIA3QdWoF6DPmdUznj3f7svbY9aPpdX\r\n         Inlg=="
+        },
+        {
+            "key": "x-google-dkim-signature",
+            "line": "X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n        d=1e100.net; s=20210112;\r\n        h=x-gm-message-state:mime-version:date:message-id:subject:from:to\r\n         :content-disposition:content-transfer-encoding;\r\n        bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n        b=HlvrMEFpDsiDF7H75BGO2E842v8CE+oq1W6GO6ke2nmfmJdGUhiM7Q5sPl7CUxCa1y\r\n         zABMXEljFClSlVzwipw+5te9T/8fD9Vbtt7f+zaQ3CnrYbJaim2eEnIxFZeIOfORU2R3\r\n         EK9601ssdV25lsrK2Wio9Gili36MYOnPySJrsx7oADvJxTXOZAqE2JHMcWSD18LAVMTU\r\n         ll0WbLLIcnVkXFOrSCZwYlGHSEl+xN19PKW89KQtgLXW5eHEX/7hM7258Q79JeAONAUh\r\n         ZB3UbKWGMZuRZCKb26uq+USTr3Rr25rk8z2n5KrToS6CA8ZhlPQtd7s/W+X/h3i2dGif\r\n         uWcg=="
+        },
+        {
+            "key": "x-gm-message-state",
+            "line": "X-Gm-Message-State: AOAM531DHTdQpJQxcbv7FL0OKPNEseEmbHaLP4n1DR1O6xhowgJshg0M\r\n\tCuD03b/6SUyxSX6sS5VClg=="
+        },
+        {
+            "key": "x-google-smtp-source",
+            "line": "X-Google-Smtp-Source: ABdhPJxhFPEXIoZIZSUvzD2hGuGVfsXAaHE6ZRDR2OKAjx+D9LfTGhwqfbhJMnR1wJoNb1Wr28JMSJk64GUgMg=="
+        },
+        {
+            "key": "mime-version",
+            "line": "MIME-Version: 1.0"
+        },
+        {
+            "key": "x-received",
+            "line": "X-Received: by 2002:ae9:ed53:0:b0:62c:da57:aa49 with SMTP id\r\n c80-20020ae9ed53000000b0062cda57aa49mr15872807qkg.570.1645613788036; Wed, 23\r\n Feb 2022 02:56:28 -0800 (PST)"
+        },
+        {
+            "key": "date",
+            "line": "Date: Tue, 22 Feb 2022 15:59:59 -0800"
+        },
+        {
+            "key": "message-id",
+            "line": "Message-ID: <3794603309710813670@google.com>"
+        },
+        {
+            "key": "subject",
+            "line": "Subject: Report domain: hedenstroem.com Submitter: google.com Report-ID: 3794603309710813670"
+        },
+        {
+            "key": "from",
+            "line": "From: noreply-dmarc-support@google.com"
+        },
+        {
+            "key": "to",
+            "line": "To: dmarc-rua@npc.hedenstroem.com"
+        },
+        {
+            "key": "content-type",
+            "line": "Content-Type: application/zip; \r\n\tname=\"google.com!hedenstroem.com!1645488000!1645574399.zip\""
+        },
+        {
+            "key": "content-disposition",
+            "line": "Content-Disposition: attachment; \r\n\tfilename=\"google.com!hedenstroem.com!1645488000!1645574399.zip\""
+        },
+        {
+            "key": "content-transfer-encoding",
+            "line": "Content-Transfer-Encoding: base64"
+        }
+    ],
+    "subject": "Report domain: hedenstroem.com Submitter: google.com Report-ID: 3794603309710813670",
+    "date": "2022-02-22T23:59:59.000Z",
+    "to": {
+        "value": [
+            {
+                "address": "dmarc-rua@npc.hedenstroem.com",
+                "name": ""
+            }
+        ],
+        "html": "<span class=\"mp_address_group\"><a href=\"mailto:dmarc-rua@npc.hedenstroem.com\" class=\"mp_address_email\">dmarc-rua@npc.hedenstroem.com</a></span>",
+        "text": "dmarc-rua@npc.hedenstroem.com"
+    },
+    "from": {
+        "value": [
+            {
+                "address": "noreply-dmarc-support@google.com",
+                "name": ""
+            }
+        ],
+        "html": "<span class=\"mp_address_group\"><a href=\"mailto:noreply-dmarc-support@google.com\" class=\"mp_address_email\">noreply-dmarc-support@google.com</a></span>",
+        "text": "noreply-dmarc-support@google.com"
+    },
+    "messageId": "<3794603309710813670@google.com>",
+    "html": false,
+    "raw": "ARC-Seal: i=1; a=rsa-sha256; t=1645613789; cv=none; d=forwardemail.net;\r\n s=default;\r\n b=Tz1L66qJb40/M0rNspnXXa7Ypl0RPmEdQFWIow8OVsR/rZrIkPq4eIeZTMtDt0Ll0XX5yJtrU\r\n N7xQDtUvgxwWNDzQ298KQkEb2PtEhkS0yQVighQ7IisCk5QekH4MLnXCm64/yodMjBpM/yeueGx\r\n c2pBHHgpyHQ/IRDpOB5p/Ag=\r\nARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=forwardemail.net; h=Content-Transfer-Encoding: Content-Type: To: From:\r\n Subject: Message-ID: Date: MIME-Version; q=dns/txt; s=default;\r\n t=1645613789; bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n b=pzV3MUKKK8uzt13rfC4vyrI+1ILiPE96IFxVc4Mukhc2GKKIkhfWWYK0x4eh6V4/JOEtmWdJx\r\n X/PoFJcWX3zpH8IO/GJLhFrXfZG/KpHqoGiOwHaueNn6aCRGFM6JdL6i2WP1VN1m5Yh144HdWWy\r\n 9RfztAqe6hbkG0tF0bbQkYc=\r\nARC-Authentication-Results: i=1; mx1.forwardemail.net;\r\n dkim=pass header.i=@google.com header.s=20210112 header.a=rsa-sha256 header.b=nFD6sQTA;\r\n spf=pass (mx1.forwardemail.net: domain of noreply-dmarc-support@google.com designates 209.85.222.202 as permitted sender)\r\n smtp.mailfrom=noreply-dmarc-support@google.com smtp.helo=mail-qk1-f202.google.com;\r\n dmarc=pass (p=REJECT arc=none) header.from=google.com header.d=google.com;\r\n bimi=none\r\nReceived-SPF: pass (mx1.forwardemail.net: domain of noreply-dmarc-support@google.com designates 209.85.222.202 as permitted sender) client-ip=209.85.222.202;\r\nAuthentication-Results: mx1.forwardemail.net;\r\n dkim=pass header.i=@google.com header.s=20210112 header.a=rsa-sha256 header.b=nFD6sQTA;\r\n spf=pass (mx1.forwardemail.net: domain of noreply-dmarc-support@google.com designates 209.85.222.202 as permitted sender)\r\n smtp.mailfrom=noreply-dmarc-support@google.com smtp.helo=mail-qk1-f202.google.com;\r\n dmarc=pass (p=REJECT arc=none) header.from=google.com header.d=google.com;\r\n bimi=none\r\nX-ForwardEmail-Sender: rfc822; noreply-dmarc-support@google.com\r\nX-ForwardEmail-Session-ID: roe5td23dh7szuxn\r\nX-ForwardEmail-Version: 8.3.0\r\nReceived: by mail-qk1-f202.google.com with SMTP id w4-20020a05620a094400b0060dd52a1445so2827056qkw.3\r\n        for <dmarc-rua@npc.hedenstroem.com>; Wed, 23 Feb 2022 02:56:28 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n        d=google.com; s=20210112;\r\n        h=mime-version:date:message-id:subject:from:to:content-disposition\r\n         :content-transfer-encoding;\r\n        bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n        b=nFD6sQTA/w8rNXXgj3LpFi1k1brhM1E/8v7nwa/DqfmJ6nH3+m7FSVVIiHR9LWc29Y\r\n         4FG8x6jzRBxssNoxNDRb8if2zT2GQq980NUFvZ5rKvNs2ltIoq5UntrJwhxG5OVhWRdy\r\n         8wJN3p9625htJgZnHobWNLengIV/byE328+FQ5tzmBj4dIPZy+BvifXVd8W4dJOevWWi\r\n         cHc8ZLbLfH+5v30HrBMt3EmOsBoZ1ZKBNIVEil4FRPm33K8MhsF/TMO4PQVpwCOYeiI6\r\n         R4SJdQ7MdIh00v901V8VAlwxPVzdfauEEP7nReIA3QdWoF6DPmdUznj3f7svbY9aPpdX\r\n         Inlg==\r\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n        d=1e100.net; s=20210112;\r\n        h=x-gm-message-state:mime-version:date:message-id:subject:from:to\r\n         :content-disposition:content-transfer-encoding;\r\n        bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n        b=HlvrMEFpDsiDF7H75BGO2E842v8CE+oq1W6GO6ke2nmfmJdGUhiM7Q5sPl7CUxCa1y\r\n         zABMXEljFClSlVzwipw+5te9T/8fD9Vbtt7f+zaQ3CnrYbJaim2eEnIxFZeIOfORU2R3\r\n         EK9601ssdV25lsrK2Wio9Gili36MYOnPySJrsx7oADvJxTXOZAqE2JHMcWSD18LAVMTU\r\n         ll0WbLLIcnVkXFOrSCZwYlGHSEl+xN19PKW89KQtgLXW5eHEX/7hM7258Q79JeAONAUh\r\n         ZB3UbKWGMZuRZCKb26uq+USTr3Rr25rk8z2n5KrToS6CA8ZhlPQtd7s/W+X/h3i2dGif\r\n         uWcg==\r\nX-Gm-Message-State: AOAM531DHTdQpJQxcbv7FL0OKPNEseEmbHaLP4n1DR1O6xhowgJshg0M\r\n\tCuD03b/6SUyxSX6sS5VClg==\r\nX-Google-Smtp-Source: ABdhPJxhFPEXIoZIZSUvzD2hGuGVfsXAaHE6ZRDR2OKAjx+D9LfTGhwqfbhJMnR1wJoNb1Wr28JMSJk64GUgMg==\r\nMIME-Version: 1.0\r\nX-Received: by 2002:ae9:ed53:0:b0:62c:da57:aa49 with SMTP id\r\n c80-20020ae9ed53000000b0062cda57aa49mr15872807qkg.570.1645613788036; Wed, 23\r\n Feb 2022 02:56:28 -0800 (PST)\r\nDate: Tue, 22 Feb 2022 15:59:59 -0800\r\nMessage-ID: <3794603309710813670@google.com>\r\nSubject: Report domain: hedenstroem.com Submitter: google.com Report-ID: 3794603309710813670\r\nFrom: noreply-dmarc-support@google.com\r\nTo: dmarc-rua@npc.hedenstroem.com\r\nContent-Type: application/zip; \r\n\tname=\"google.com!hedenstroem.com!1645488000!1645574399.zip\"\r\nContent-Disposition: attachment; \r\n\tfilename=\"google.com!hedenstroem.com!1645488000!1645574399.zip\"\r\nContent-Transfer-Encoding: base64\r\n\r\nUEsDBAoAAAAIAEBUV1TO0occVQIAAIYWAAA0AAAAZ29vZ2xlLmNvbSFoZWRlbnN0cm9lbS5jb20h\r\nMTY0NTQ4ODAwMCExNjQ1NTc0Mzk5LnhtbO1Yy46jMBC8z1dEucfg8EpWDLOn/YLdM3JMQ6wB27LN\r\nPP5+TTCEIfvQSHtYRT4Fl7vb1dVlJJI/vXXt5gWUZoI/bjEKtxvgVFSMN4/bH9+/7Q7bzVPxkNcA\r\n1YnQ5+Jhs8kVSKFM2YEhFTFkwCwqVFNy0kHRCNG0gKjo8mAGxxjoCGsLLmyF9n1XdUTRne7lUO7r\r\nMm2MczlvRpGSCm4INSXjtSjOxkj9JQhcKrqmBiQgXL+CCvZxmiaH0Na6zR8LuzZYVUTZMU7DKAqP\r\nGQ4POEozm3fdHsNtq1AqwhvXjIVO0DBe4DRO4sMhDG3SiEz7wKvLbpLF0fFoqfCpWPCx2nzaUtNc\r\nipbR91L2p5bpM8xEhFWHFxYAro0S0I2iOXwMItUz6wqVB+ODA7WsL9jwO0LSToNDHki31hOgJ0RS\r\nU+Cht+HhwvVXvKyaVKiJohKvswha9IpCyaStEqMoRBjhZG8PmPEpkoqe27PyYHyYYHccvJC2t6pV\r\n08YgBdNSaGasex3tJbKIGzSQRGsbMMvh+q3dxqzJosXVmXZMU2M5s+obVjN7d+a0M5AKVFkr0d2O\r\nZ7npqt3UyElvzqUC3bfmWnZF2c35ckcQbUVf1S1RgDiYjyZwLh+KuR7dYtE+tECNUMU+3A8zmZaz\r\nEMuzf0Pkj0b8DAeN/8pgMaR/RGAxeHsxVvIPwZOtP+HwPcLZ0TvcO/yOHZ54g3uD36/BY4Rx5h3u\r\nHb7mMH5r3InLExSl3uTe5GsOd/MaTxAOsXe4d/iaw129xmMUe497j685/M9v8Ty4/qX8E1BLAQIK\r\nAAoAAAAIAEBUV1TO0occVQIAAIYWAAA0AAAAAAAAAAAAAAAAAAAAAABnb29nbGUuY29tIWhlZGVu\r\nc3Ryb2VtLmNvbSExNjQ1NDg4MDAwITE2NDU1NzQzOTkueG1sUEsFBgAAAAABAAEAYgAAAKcCAAAA\r\nAA==\r\n"
+}
\ No newline at end of file
diff --git a/types/dmarc.go b/types/dmarc.go
index 276e7f7d0e351b6f429dfb5c789fe393ec4c1586..88ec2df5ce0944ebed259f5726d4d1d1222c2d43 100644
--- a/types/dmarc.go
+++ b/types/dmarc.go
@@ -1,149 +1,90 @@
 package types
 
 import (
-	"bytes"
-	"encoding/xml"
-	"time"
+	"net"
 )
 
-// Content is the structure for processing data
-type Content struct {
-	From string
-	Name string
-	Data *bytes.Buffer
+// DateRange time period
+type DateRange struct {
+	Begin int64 `xml:"begin"`
+	End   int64 `xml:"end"`
 }
 
-// Row is the dmarc row in a report
-type Row struct {
-	SourceIP        string
-	Count           int64
-	EvalDisposition string
-	EvalSPFAlign    string
-	EvalDKIMAalign  string
-	Reason          string
-	DKIMDomain      string
-	DKIMResult      string
-	SPFDomain       string
-	SPFResult       string
-	IdentifierHFrom string
-}
-
-// Rows is jus the report and the rows of a report
-type Rows struct {
-	Report Report
-	Rows   []Row
-}
-
-// Report is the content of the report
-type Report struct {
-	ID                     int64
-	ReportBegin            time.Time
-	ReportEnd              time.Time
-	PolicyDomain           string
-	ReportOrg              string
-	ReportID               string
-	ReportEmail            string
-	ReportExtraContactInfo string
-	PolicyAdkim            string
-	PolicyAspf             string
-	PolicyP                string
-	PolicySP               string
-	PolicyPCT              string
-	Count                  int64
-	DKIMResult             string
-	SPFResult              string
-	Items                  int
-}
-
-// Reports is the collection of reports
-type Reports struct {
-	Reports    []Report
-	LastPage   int
-	CurPage    int
-	NextPage   int
-	TotalPages int
-	Pages      []int
-}
-
-type dateRange struct {
-	XMLName xml.Name `xml:"date_range"`
-	Begin   int64    `xml:"begin"`
-	End     int64    `xml:"end"`
-}
-
-type reportMetadata struct {
-	XMLName          xml.Name  `xml:"report_metadata"`
+// ReportMetadata for the report
+type ReportMetadata struct {
 	OrgName          string    `xml:"org_name"`
 	Email            string    `xml:"email"`
-	ExtraContactInfo string    `xml:"extra_contact_info,omitempty"`
+	ExtraContactInfo string    `xml:"extra_contact_info"`
 	ReportID         string    `xml:"report_id"`
-	DateRange        dateRange `xml:"date_range"`
+	Date             DateRange `xml:"date_range"`
+	Errors           []string  `xml:"error"`
 }
 
-type policyPublished struct {
-	XMLName xml.Name `xml:"policy_published"`
-	Domain  string   `xml:"domain"`
-	ADKIM   string   `xml:"adkim"`
-	ASPF    string   `xml:"aspf"`
-	P       string   `xml:"p"`
-	SP      string   `xml:"sp"`
-	PCT     string   `xml:"pct"`
+// PolicyPublished found in DNS
+type PolicyPublished struct {
+	Domain string `xml:"domain"`
+	ADKIM  string `xml:"adkim"`
+	ASPF   string `xml:"aspf"`
+	P      string `xml:"p"`
+	SP     string `xml:"sp"`
+	Pct    int    `xml:"pct"`
+	Fo     string `xml:"fo"`
 }
 
-type reason struct {
-	XMLName xml.Name `xml:"reason"`
-	Type    string   `xml:"type"`
-	Comment string   `xml:"comment"`
+// PolicyEvaluated what was evaluated
+type PolicyEvaluated struct {
+	Disposition string                 `xml:"disposition"`
+	DKIM        string                 `xml:"dkim"`
+	SPF         string                 `xml:"spf"`
+	Reasons     []PolicyOverrideReason `xml:"reason,omitempty"`
 }
 
-type policyEvaluated struct {
-	XMLName     xml.Name `xml:"policy_evaluated"`
-	Disposition string   `xml:"disposition"`
-	DKIM        string   `xml:"dkim"`
-	SPF         string   `xml:"spf"`
-	Reasons     []reason `xml:"reason"`
+// PolicyOverrideReason are the reasons that may affect DMARC disposition
+// or execution thereof
+type PolicyOverrideReason struct {
+	Type    string `xml:"type"`
+	Comment string `xml:"comment"`
 }
 
-type row struct {
-	XMLName         xml.Name        `xml:"row"`
-	SourceIP        string          `xml:"source_ip"`
-	Count           int64           `xml:"count"`
-	PolicyEvaluated policyEvaluated `xml:"policy_evaluated"`
-}
-
-type identify struct {
-	XMLName    xml.Name `xml:"identifiers"`
-	HeaderFrom string   `xml:"header_from"`
+// Row for each IP address
+type Row struct {
+	SourceIP net.IP          `xml:"source_ip"`
+	Count    int             `xml:"count"`
+	Policy   PolicyEvaluated `xml:"policy_evaluated"`
 }
 
-type spf struct {
-	XMLName xml.Name `xml:"spf"`
-	Result  string   `xml:"result"`
+// Identifiers headers checked
+type Identifiers struct {
+	HeaderFrom   string `xml:"header_from"`
+	EnvelopeFrom string `xml:"envelope_from"`
+	EnvelopeTo   string `xml:"envelope_to,omitempty"`
 }
 
-type dkim struct {
-	XMLName xml.Name `xml:"dkim"`
-	Result  string   `xml:"result"`
+// Result for each IP
+type Result struct {
+	Domain      string `xml:"domain"`
+	Selector    string `xml:"selector"`
+	Result      string `xml:"result"`
+	HumanResult string `xml:"human_result"`
 }
 
-type authResult struct {
-	XMLName xml.Name `xml:"auth_results"`
-	SPF     []spf    `xml:"spf"`
-	DKIM    []dkim   `xml:"dkim"`
+// AuthResults for DKIM/SPF
+type AuthResults struct {
+	DKIM []Result `xml:"dkim,omitempty"`
+	SPF  []Result `xml:"spf,omitempty"`
 }
 
-type record struct {
-	XMLName     xml.Name   `xml:"record"`
-	Rows        []row      `xml:"row"`
-	Identifiers identify   `xml:"identifiers"`
-	AuthResults authResult `xml:"auth_results"`
+// Record for each IP
+type Record struct {
+	Row         Row         `xml:"row"`
+	Identifiers Identifiers `xml:"identifiers"`
+	AuthResults AuthResults `xml:"auth_results"`
 }
 
-// Feedback contains the reports and file information
+// Feedback the report itself
 type Feedback struct {
-	XMLName         xml.Name `xml:"feedback"`
-	FromFile        string
-	ReportMetadata  reportMetadata  `xml:"report_metadata"`
-	PolicyPublished policyPublished `xml:"policy_published"`
-	Records         []record        `xml:"record"`
+	Version  float32         `xml:"version"`
+	Metadata ReportMetadata  `xml:"report_metadata"`
+	Policy   PolicyPublished `xml:"policy_published"`
+	Records  []Record        `xml:"record"`
 }
diff --git a/utils/dmarc.go b/utils/dmarc.go
new file mode 100644
index 0000000000000000000000000000000000000000..ef13526e6b9f0d1fb885917c69a0ad7befb051c7
--- /dev/null
+++ b/utils/dmarc.go
@@ -0,0 +1,197 @@
+package utils
+
+import (
+	"encoding/xml"
+	"errors"
+	"net/http"
+
+	dto "github.com/prometheus/client_model/go"
+	"github.com/prometheus/common/log"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types"
+)
+
+var prometheusLabels = []string{
+	"reporter",
+	"from_domain",
+	"dkim_domain",
+	"spf_domain",
+}
+
+var TotalCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_total",
+		Help: "Number of reported messages",
+	},
+	prometheusLabels,
+)
+
+var CompliantCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_compliant",
+		Help: "Number of DMARC compliant messages",
+	},
+	prometheusLabels,
+)
+
+var QuarantineCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_quarantine",
+		Help: "Number of quarantined messages",
+	},
+	prometheusLabels,
+)
+
+var RejectCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_reject",
+		Help: "Number of rejected messages",
+	},
+	prometheusLabels,
+)
+
+var SPFAlignedCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_spf_aligned",
+		Help: "Number of SPF aligned messages",
+	},
+	prometheusLabels,
+)
+
+var SPFPassCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_spf_pass",
+		Help: "Number of messages with raw SPF pass",
+	},
+	prometheusLabels,
+)
+
+var DKIMAlignedCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_dkim_aligned",
+		Help: "Number of DKIM aligned messages",
+	},
+	prometheusLabels,
+)
+
+var DKIMPassCounter = promauto.NewCounterVec(
+	prometheus.CounterOpts{
+		Name: "dmarc_dkim_pass",
+		Help: "Number of messages with raw DKIM pass",
+	},
+	prometheusLabels,
+)
+
+var counters = map[string]*prometheus.CounterVec{
+	"dmarc_total":        TotalCounter,
+	"dmarc_compliant":    CompliantCounter,
+	"dmarc_quarantine":   QuarantineCounter,
+	"dmarc_reject":       RejectCounter,
+	"dmarc_spf_aligned":  SPFAlignedCounter,
+	"dmarc_spf_pass":     SPFPassCounter,
+	"dmarc_dkim_aligned": DKIMAlignedCounter,
+	"dmarc_dkim_pass":    DKIMPassCounter,
+}
+
+func getLabelValues(metric *dto.Metric) ([]string, error) {
+	var v []string
+	for _, l := range prometheusLabels {
+		for _, lv := range metric.Label {
+			if l == *lv.Name {
+				v = append(v, *lv.Value)
+			}
+		}
+	}
+	if len(v) == len(prometheusLabels) {
+		return v, nil
+	}
+	return nil, errors.New("missing labels")
+}
+
+func Restore(mf map[string]*dto.MetricFamily) {
+	for name, counter := range counters {
+		if family, found := mf[name]; found {
+			for _, metric := range family.Metric {
+				if l, err := getLabelValues(metric); err == nil {
+					v := *metric.Counter.Value
+					counter.WithLabelValues(l...).Add(v)
+				} else {
+					log.Warn(err)
+				}
+			}
+		}
+	}
+}
+
+func Parse(body []byte) ([]types.Feedback, error) {
+	contentType := http.DetectContentType(body)
+	switch contentType {
+	case "application/zip":
+		if files, err := Unzip(body); err == nil {
+			var reports []types.Feedback
+			for _, file := range files {
+				r, err := Parse(file.Data)
+				if err != nil {
+					return reports, err
+				}
+				reports = append(reports, r...)
+			}
+			return reports, nil
+		} else {
+			return nil, err
+		}
+	case "application/gzip":
+	case "application/x-gzip":
+		data, err := GUnzip(body)
+		if err != nil {
+			return nil, err
+		}
+		return Parse(data)
+	case "text/xml":
+	case "text/xml; charset=utf-8":
+		var feedback types.Feedback
+		if err := xml.Unmarshal(body, &feedback); err == nil {
+			return []types.Feedback{feedback}, nil
+		} else {
+			return nil, err
+
+		}
+	}
+	return nil, errors.New("unsupported content type: " + contentType)
+}
+
+func Analyze(reports []types.Feedback) {
+	for _, report := range reports {
+		for _, r := range report.Records {
+			LabelValues := []string{
+				report.Metadata.OrgName,
+				r.Identifiers.HeaderFrom,
+				r.AuthResults.DKIM[0].Domain,
+				r.AuthResults.SPF[0].Domain,
+			}
+			TotalCounter.WithLabelValues(LabelValues...).Inc()
+			switch r.Row.Policy.Disposition {
+			case "quarantine":
+				QuarantineCounter.WithLabelValues(LabelValues...).Inc()
+			case "reject":
+				RejectCounter.WithLabelValues(LabelValues...).Inc()
+			case "none":
+				CompliantCounter.WithLabelValues(LabelValues...).Inc()
+			}
+			if r.Row.Policy.SPF == "pass" {
+				SPFAlignedCounter.WithLabelValues(LabelValues...).Inc()
+			}
+			if r.AuthResults.SPF[0].Result == "pass" {
+				SPFPassCounter.WithLabelValues(LabelValues...).Inc()
+			}
+			if r.Row.Policy.DKIM == "pass" {
+				DKIMAlignedCounter.WithLabelValues(LabelValues...).Inc()
+			}
+			if r.AuthResults.DKIM[0].Result == "pass" {
+				DKIMPassCounter.WithLabelValues(LabelValues...).Inc()
+			}
+		}
+	}
+}
diff --git a/utils/zip.go b/utils/zip.go
index fd60f6f69b734e58bc1a5131d46789a42361cae6..5e4e951991dc81a1fc3cc4c22d72d4b0918be9b3 100644
--- a/utils/zip.go
+++ b/utils/zip.go
@@ -3,12 +3,29 @@ package utils
 import (
 	"archive/zip"
 	"bytes"
+	"compress/gzip"
 	"io/ioutil"
 
 	"gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types"
 )
 
-func UnzipBytes(b []byte) ([]types.ZipFile, error) {
+func GUnzip(b []byte) ([]byte, error) {
+	// TODO: prevent zip bombs
+	reader, err := gzip.NewReader(bytes.NewBuffer(b))
+	if err != nil {
+		return nil, err
+	}
+	defer reader.Close()
+	var buffer bytes.Buffer
+	_, err = buffer.ReadFrom(reader)
+	if err != nil {
+		return nil, err
+	}
+	return buffer.Bytes(), nil
+}
+
+func Unzip(b []byte) ([]types.ZipFile, error) {
+	// TODO: prevent zip bombs
 	zipReader, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
 	if err != nil {
 		return nil, err