diff --git a/brian_polling_manager/error_report/cli.py b/brian_polling_manager/error_report/cli.py
index 305da32a8d689bdd277662d1c73f175acdf03230..4d6da0453986e64041f860ee6f4bdcc0b759b77d 100644
--- a/brian_polling_manager/error_report/cli.py
+++ b/brian_polling_manager/error_report/cli.py
@@ -35,7 +35,17 @@ PROCESSED_ERROR_COUNTERS_SCHEMA = {
     "definitions": {
         "error_counters_content": {
             "type": "object",
-            "properties": {"additionalProperties": {"type": "integer"}},
+            "properties": {
+                "framing-errors": {"type": "integer"},
+                "bit-error-seconds": {"type": "integer"},
+                "errored-blocks-seconds": {"type": "integer"},
+                "input-crc-errors": {"type": "integer"},
+                "input-total-errors": {"type": "integer"},
+                "input-discards": {"type": "integer"},
+                "input-drops": {"type": "integer"},
+                "output-drops": {"type": "integer"},
+            },
+            "additionalProperties": False,
         },
         "interface_error_counters": {
             "type": "object",
@@ -45,16 +55,14 @@ PROCESSED_ERROR_COUNTERS_SCHEMA = {
                 "description": {"type": "string"},
                 "error_counters": {"$ref": "#/definitions/error_counters_content"},
                 "diff": {"$ref": "#/definitions/error_counters_content"},
-                "has_new_errors": {"type": "boolean"},
             },
             "required": [
                 "router",
                 "interface",
                 "description",
                 "error_counters",
-                "diff",
-                "has_new_errors",
             ],
+            "additionalProperties": False,
         },
         "excluded_interface_error_counters": {
             "type": "object",
@@ -70,17 +78,18 @@ PROCESSED_ERROR_COUNTERS_SCHEMA = {
                 "description",
                 "error_counters",
             ],
+            "additionalProperties": False,
         },
     },
     "type": "object",
     "properties": {
         "interfaces": {
             "type": "array",
-            "item": {"$ref": "#/definitions/interface_error_counters"},
+            "items": {"$ref": "#/definitions/interface_error_counters"},
         },
         "excluded_interfaces": {
             "type": "array",
-            "item": {"$ref": "#/definitions/excluded_interface_error_counters"},
+            "items": {"$ref": "#/definitions/excluded_interface_error_counters"},
         },
     },
     "required": ["interfaces", "excluded_interfaces"],
@@ -283,7 +292,7 @@ def main():
             all_error_counters,
             date=datetime.utcnow().strftime("%a %d %b %H:%M:%S UTC %Y"),
         )
-        print(body)
+
 
     # TODO: ensure data is from the day that we're interested in (today or yesterday)
     # TODO: send script failures to admin email
diff --git a/brian_polling_manager/error_report/config-example.json b/brian_polling_manager/error_report/config-example.json
index e7e23625b7e91eb139aa72f51e7a0672e94137a4..336a28d2b33abfda5a0591715518882fba7cd7a0 100644
--- a/brian_polling_manager/error_report/config-example.json
+++ b/brian_polling_manager/error_report/config-example.json
@@ -1,8 +1,9 @@
 {
   "email": {
     "from": "noreply@geant.org",
-    "reply-to": "noreply@geant.org",
+    "reply_to": "noreply@geant.org",
     "to": "some-bogus-email",
+    "cc": "some-cc",
     "contact": "someone@geant.org / NE team"
   },
   "inventory": ["blah"],
diff --git a/brian_polling_manager/error_report/config.py b/brian_polling_manager/error_report/config.py
index 7cad8f1fd25bfe131cdb39eaa3d08fe0a5b8b698..e39177a7fecf2dd2b30fb2c6188b0e0a73aefae7 100644
--- a/brian_polling_manager/error_report/config.py
+++ b/brian_polling_manager/error_report/config.py
@@ -13,8 +13,9 @@ CONFIG_SCHEMA = {
             "type": "object",
             "properties": {
                 "from": {"type": "string"},
-                "reply-to": {"type": "string"},
+                "reply_to": {"type": "string"},
                 "to": {"type": "string"},
+                "cc": {"type": "string"},
                 "contact": {"type": "string"},
             },
             "additionalProperties": False,
diff --git a/brian_polling_manager/error_report/error_report.html.jinja2 b/brian_polling_manager/error_report/error_report.html.jinja2
index 808e8ade7eeafe708b4084f9a7186171ba8dd42f..cb7371acbb375a023dc6fdc4262c9e3b6ff6a158 100644
--- a/brian_polling_manager/error_report/error_report.html.jinja2
+++ b/brian_polling_manager/error_report/error_report.html.jinja2
@@ -1,6 +1,9 @@
 <html>
 <body>
 <pre>
+{#- We mix tabs with spaces and have otherwise inconsistent whitespace to keep the
+    output as close as possible to the output of the original script
+#}
 {%- for ifc in interfaces  %}
 =================================
 {{ ifc.router }}
diff --git a/test/error_report/test_error_report.py b/test/error_report/test_error_report.py
index 853fb291740fb9f3849b52c226b1f2da4635d22b..68196ac4d902e0696824a1e3caa3d4ed92566b27 100644
--- a/test/error_report/test_error_report.py
+++ b/test/error_report/test_error_report.py
@@ -3,6 +3,7 @@ import json
 import pathlib
 from unittest.mock import Mock, patch, call
 
+from brian_polling_manager.error_report.mailer import render_html
 import jsonschema
 import pytest
 from brian_polling_manager.error_report import cli, config
@@ -130,7 +131,7 @@ def test_validate_config(tmp_path):
     content = {
         "email": {
             "from": "noreply@geant.org",
-            "reply-to": "noreply@geant.org",
+            "reply_to": "noreply@geant.org",
             "to": "some-bogus-email",
             "contact": "someone@geant.org / NE team",
         },
@@ -386,3 +387,85 @@ def test_processes_excluded_interface(create_error_point, get_interface_errors):
             },
         }
     ]
+
+
+def test_render_html(create_error_point, get_interface_errors):
+    create_error_point(
+        "mx1.ams.nl.geant.net", "ae1", "yesterday", input_drops=1, framing_errors=2
+    )
+    create_error_point(
+        "mx1.ams.nl.geant.net", "ae1", "today", input_drops=2, framing_errors=4
+    )
+
+    create_error_point("mx1.fra.de.geant.net", "ae10", "today", input_drops=3)
+    errors = get_interface_errors()
+    result = render_html(errors=errors, date="<some date>")
+    # careful, there are tabs mixed with spaces here, but hey, we want to keep the
+    # output as close to the original script
+    expected = """\
+<html>
+<body>
+<pre>
+=================================
+mx1.ams.nl.geant.net
+=================================
+	ae1	         PHY blah blah
+		framing-errors		4		Diff:	2
+		input-drops	2		Diff:	1
+
+=================================
+mx1.fra.de.geant.net
+=================================
+	ae10	         PHY blah blah foo
+		input-drops	3
+
+
+
+
+Generated <some date>
+</pre>
+</body>
+</html>"""
+    assert result == expected.replace("\n", "\r\n")
+
+
+def test_render_html_with_exclusions(create_error_point, get_interface_errors):
+    create_error_point("mx1.ams.nl.geant.net", "ae1", "today", input_drops=2)
+
+    create_error_point(
+        "mx1.fra.de.geant.net",
+        "ae10",
+        "today",
+        # mess up order of kwargs to test re-ordering
+        bit_error_seconds=2,
+        framing_errors=1,
+        input_crc_errors=4,
+        errored_blocks_seconds=3,
+        input_discards=6,
+        input_total_errors=5,
+        output_drops=8,
+        input_drops=7,
+    )
+    errors = get_interface_errors(exclusions=["foo"])
+    result = render_html(errors=errors, date="<some date>")
+    # careful, mixing tabs with spaces here
+    expected = """\
+<html>
+<body>
+<pre>
+=================================
+mx1.ams.nl.geant.net
+=================================
+	ae1	         PHY blah blah
+		input-drops	2
+
+ROUTER,INTERFACE,FRAMING ERRORS,BIT ERROR SECONDS,ERRORED BLOCKS SECONDS,CRC ERRORS,TOTAL ERRORS,INPUT DISCARDS,INPUT DROPS,OUTPUT DROPS
+mx1.fra.de.geant.net,ae10,1,2,3,4,5,6,7,8,PHY blah blah foo
+
+
+
+Generated <some date>
+</pre>
+</body>
+</html>"""
+    assert result == expected.replace("\n", "\r\n")