diff --git a/.gitignore b/.gitignore
index 21f22a70ae23594913bfed26516ab580ad05ca6f..0d08c43e1db932724bb1829dcf2bb1641b12a7f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 .bash*
 .profile*
 env/
+.flaskenv
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..77d3693c1bbbfe6320959cb7bf7f4fd2cbdd76d4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,125 @@
+# Webhook to Matrix Hookshot
+
+This API translates incoming webhooks into generic webhooks (hookshot).
+
+1. Receive Grafana webhook alert
+2. Prepare alert data (error handling, add additional values)
+3. Apply data to selected template (html, text)
+4. Send out notification to hookshot
+
+## Configuration
+
+`https://webhooks.mbot.ethz.ch/webhook/grafana/<hook>?template=<template>&version=<version>`
+
+| Param      | Description                     | Optional |
+| ---------- | ------------------------------- | -------- |
+| `hook`     | Hookshot ID of your webhook     |          |
+| `template` | Template name (see table below) | Yes      |
+| `version`  | API version (currently ignored) | Yes      |
+
+This is the URL needed by Grafana. Create a new contact point and paste it.
+
+## How to run
+
+`flask --app app --debug run`
+
+Set hostname and port with `-h` and `-p` respectively.
+
+## Templates
+
+| Name             | Description                 |
+| ---------------- | --------------------------- |
+| `default`        | shows important values      |
+| `oneliner`       | `default` but more concise  |
+| `detailed`       | `default` but better        |
+| `detailed_table` | `detailed` with html tables |
+
+For mor details see [templates](/templates.md).
+
+## Jinja Templates
+
+| `<template_name>` |                                                           |
+| ----------------- | --------------------------------------------------------- |
+| Path HTML         | `template/<template_name>.html.jinja`                     |
+| Path Text         | `template/<template_name>.txt.jinja`                      |
+| Registration      | Append `<template_name>` to `msg_templates` `(config.py)` |
+
+- Info
+  - Template rendering context (variables available in the template):
+    - Incoming grafana alert data
+    - Additional values (see below)
+  - Error handling: missing/invalid data is interpreted as empty/zero
+- Keep in mind
+  - Use `alert['values']` (and not `alert.values`) to keep Jinja happy
+  - Only use html tags allowed by the matrix spec
+  - Remove unnecessary whitespace
+
+### Rendering Context
+
+You can use every value provided by Grafana. For a complete list see [Grafana Docs](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/).
+
+#### Reserved / Special Values
+
+These values come directly from Grafana but have a special meaning or are used to
+compute additional values and are thus explained here.
+
+|                     | Value                              | Description |
+| ------------------- | ---------------------------------- | ----------- |
+| Reserved Labels     | `alert.labels['alertname']`        |             |
+|                     | `alert.labels['grafana_folder']`   |             |
+| Special Annotations | `alert.annotations['description']` |             |
+|                     | `alert.annotations['summary]`      |             |
+| Timestamps          | `alert['startsAt']`                |             |
+|                     | `alert['endsAt']`                  |             |
+
+**Note:** Reserved labels / special annotations also apply to `commonLabels`, `commonAnnotations`
+
+#### Additional Values
+
+These values are added to the incoming Grafana alert which is then applied to the Jinja template
+
+```jsonc
+{
+  "normalLabels": {},
+  "normalAnnotations": {},
+  "alerts": [
+    {
+      "uniqueLabels": {},
+      "uniqueAnnotations": {},
+      "valuesForJinja": {},
+      "startsAtParsed": "",
+      "endsAtParsed": "",
+    },
+    (...)
+  ],
+  "commonStartsAtParsed": "",
+  "commonEndsAtParsed": "",
+}
+```
+
+| Value                  | Description                                      |
+| ---------------------- | ------------------------------------------------ |
+| `normalLabels`         | Labels that are not "reserved"                   |
+| `normalAnnotations`    | Annotations that are not "special"               |
+| `uniqueLabels`         | Labels not in `commonLabels`                     |
+| `uniqueAnnotations`    | Annotations not in `commonAnnotations`           |
+| `startsAtParsed`       | Formatted version of `startsAt`                  |
+| `endsAtParsed`         | Formatted version of `endsAt`                    |
+| `commonStartsAtParsed` | `startsAtParsed` if same on every alert instance |
+| `commonEndsAtParsed`   | `endsAtParsed` if same on every alert instance   |
+
+### Inherit Template
+
+Inherit from another text template if you only want to change html
+
+```jinja
+{%- include "detailed.txt.jinja" -%}
+```
+
+## Links
+
+- [Matrix message spec](https://spec.matrix.org/latest/client-server-api/#mroommessage-msgtypes)
+- [Hookshot webhook handling](https://matrix-org.github.io/matrix-hookshot/latest/setup/webhooks.html#webhook-handling)
+- Grafana
+  - [Alerts webhook format](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/)
+  - [Labels and annotations](https://grafana.com/docs/grafana/next/alerting/fundamentals/alert-rules/annotation-label/)
diff --git a/_screenshots/hookshot-create.png b/_screenshots/hookshot-create.png
new file mode 100644
index 0000000000000000000000000000000000000000..8dbe31a6519ebeb9e3fc08229a0be063152ca703
Binary files /dev/null and b/_screenshots/hookshot-create.png differ
diff --git a/_screenshots/templates/default-cpu_usage.html.png b/_screenshots/templates/default-cpu_usage.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ccadc0d65596b3276f34511885daea8388f04ec
Binary files /dev/null and b/_screenshots/templates/default-cpu_usage.html.png differ
diff --git a/_screenshots/templates/default.html.png b/_screenshots/templates/default.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6739695fa91c902b7cde59c240d06688a8325b3
Binary files /dev/null and b/_screenshots/templates/default.html.png differ
diff --git a/_screenshots/templates/detailed-cpu_usage.html.png b/_screenshots/templates/detailed-cpu_usage.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..2f4f634bf62ca39bfa6beb23da592f24fb8f7dcc
Binary files /dev/null and b/_screenshots/templates/detailed-cpu_usage.html.png differ
diff --git a/_screenshots/templates/detailed.html.png b/_screenshots/templates/detailed.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..33d6c433b8324309da25965f15e94fed1ca77771
Binary files /dev/null and b/_screenshots/templates/detailed.html.png differ
diff --git a/_screenshots/templates/detailed_table-cpu_usage0.html.png b/_screenshots/templates/detailed_table-cpu_usage0.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..3867e8b8de6d294005b62efc2c396babbf7214e7
Binary files /dev/null and b/_screenshots/templates/detailed_table-cpu_usage0.html.png differ
diff --git a/_screenshots/templates/detailed_table-cpu_usage1.html.png b/_screenshots/templates/detailed_table-cpu_usage1.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7c5b788f3a652fe0bfed2f55c88ff1c38b3b7ac
Binary files /dev/null and b/_screenshots/templates/detailed_table-cpu_usage1.html.png differ
diff --git a/_screenshots/templates/detailed_table-humidity.html.png b/_screenshots/templates/detailed_table-humidity.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..a6dda6d9894502a7043a164ac1b143e68a00f6ef
Binary files /dev/null and b/_screenshots/templates/detailed_table-humidity.html.png differ
diff --git a/_screenshots/templates/detailed_table-temp.html.png b/_screenshots/templates/detailed_table-temp.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..28259ea17726d24157f30e8cf21c29946db97907
Binary files /dev/null and b/_screenshots/templates/detailed_table-temp.html.png differ
diff --git a/_screenshots/templates/detailed_table-wetbulb.html.png b/_screenshots/templates/detailed_table-wetbulb.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..bbcbb8f427b2af497900e9198b8ae97fe8d846a9
Binary files /dev/null and b/_screenshots/templates/detailed_table-wetbulb.html.png differ
diff --git a/_screenshots/templates/oneliner-cpu_usage.html.png b/_screenshots/templates/oneliner-cpu_usage.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..6a1469e4a5d4d7fdf60c6125989f3da107755123
Binary files /dev/null and b/_screenshots/templates/oneliner-cpu_usage.html.png differ
diff --git a/_screenshots/templates/oneliner.html.png b/_screenshots/templates/oneliner.html.png
new file mode 100644
index 0000000000000000000000000000000000000000..37b2b06898083a3c90a7017b079eea700a3bfefe
Binary files /dev/null and b/_screenshots/templates/oneliner.html.png differ
diff --git a/app.py b/app.py
index c5eff58bf897466c21269a0efaebde4ed3ea8dec..569681a22ea61534262d3957347140dcbcfbbc42 100644
--- a/app.py
+++ b/app.py
@@ -1,119 +1,100 @@
 import requests
-from flask import Flask, request, make_response, render_template
-
-from config import url, hookshot_params, flask_params, msg_templates, msg_template_default
-from validate import grafana_validate_incoming
+from flask import Flask, make_response, render_template, request
 
+from config import (
+    flask_params,
+    hookshot_params,
+    msg_template_default,
+    msg_templates,
+    url,
+)
+from sanitize import grafana_sanitize_incoming
 
 app = Flask(__name__, **flask_params)
+app.jinja_env.lstrip_blocks = True
+app.jinja_env.trim_blocks = True
 
 
-@app.route("/webhook/slack/<hook>", methods=['POST'])
+@app.route("/webhook/slack/<hook>", methods=["POST"])
 def slack(hook):
-    plain = ''
-    html = ''
+    plain = ""
+    html = ""
     incoming = request.json
-    print('Got incoming /slack hook: ' + str(incoming))
+    print("Got incoming /slack hook: " + str(incoming))
 
-    attachments = incoming.get('attachments', [])
-    username = str(incoming.get('username', ''))
+    attachments = incoming.get("attachments", [])
+    username = str(incoming.get("username", ""))
 
     for attachment in attachments:
-        color = str(attachment.get('color', '')).lower()
-        title = str(attachment.get('title', ''))
-        title_link = str(attachment.get('title_link', ''))
-        text = str(attachment.get('text', ''))
-        footer = str(attachment.get('footer', ''))
-        fields = attachment.get('fields', [])
+        color = str(attachment.get("color", "")).lower()
+        title = str(attachment.get("title", ""))
+        title_link = str(attachment.get("title_link", ""))
+        text = str(attachment.get("text", ""))
+        footer = str(attachment.get("footer", ""))
+        fields = attachment.get("fields", [])
 
-        html += '<font color="' + color + '">' if color else ''
+        html += '<font color="' + color + '">' if color else ""
 
         if title and title_link:
-            plain += title + ' ' + title_link + '\n'
-            html += '<b><a href="' + title_link + '">' + title + '</a></b><br/>\n'
+            plain += title + " " + title_link + "\n"
+            html += '<b><a href="' + title_link + '">' + title + "</a></b><br/>\n"
         elif title:
-            plain += title + '\n'
-            html += '<b>' + title + '</b><br/>\n'
+            plain += title + "\n"
+            html += "<b>" + title + "</b><br/>\n"
 
         if text:
-            plain += text + '\n'
-            html += text + '<br/>\n'
+            plain += text + "\n"
+            html += text + "<br/>\n"
 
         for field in fields:
-            title = str(field.get('title', ''))
-            value = str(field.get('value', ''))
+            title = str(field.get("title", ""))
+            value = str(field.get("value", ""))
             if title and value:
-                plain += title + ': ' + value + '\n'
-                html += '<b>' + title + '</b>: ' + value + '<br/>\n'
+                plain += title + ": " + value + "\n"
+                html += "<b>" + title + "</b>: " + value + "<br/>\n"
 
         if footer:
-            plain += footer + '\n'
-            html += footer + '<br/>\n'
+            plain += footer + "\n"
+            html += footer + "<br/>\n"
 
-        html += '</font>' if color else ''
+        html += "</font>" if color else ""
 
     if plain and html:
         if username:
-            json = {'text':plain,'html':html,'username':username}
+            json = {"text": plain, "html": html, "username": username}
         else:
-            json = {'text':plain,'html':html}
-        print('Sending hookshot: ' + str(json))
+            json = {"text": plain, "html": html}
+        print("Sending hookshot: " + str(json))
         r = requests.post(url + hook, json=json)
     else:
-        print('Invalid format, sending unmodified.')
+        print("Invalid format, sending unmodified.")
         r = requests.post(url + hook, json=incoming)
 
-    response = make_response('ok', 200)
+    response = make_response("ok", 200)
     response.mimetype = "text/plain"
     return response
 
-@app.route("/webhook/grafana/<hook>", methods=['POST', 'PUT'])
-def grafana(hook):
-    """
-    see https://grafana.com/docs/grafana/latest/alerting/alerting-rules/manage-contact-points/integrations/webhook-notifier/
-    todo:
-    - handle query params to select different templates
-    - handle empty -> default
-    """
 
+@app.route("/webhook/grafana/<hook>", methods=["POST", "PUT"])
+def grafana(hook):
     args = request.args
-    if "template" in args.keys():
-        template_type = args.get("template")
-        if template_type not in msg_templates:
-            template_type = msg_template_default
-    else:
+    
+    template_type = args.get("template", msg_template_default)
+    if template_type not in msg_templates:
         template_type = msg_template_default
 
-    if "version" in args.keys():
-        version = args.get("version")
-    else:
-        version = "v2"
-
-    print(args)
-    print(f"template: {template_type}, version: {version}")
+    version = args.get("version", "v2")  # unused at the moment
 
     incoming = request.json
-    print('Got incoming /grafana hook: ' + str(incoming))
+    print("Got incoming /grafana hook: " + str(incoming))
 
-    if not isinstance(incoming, dict):
-        incoming = dict()
-
-    grafana_validate_incoming(incoming)
-
-    t = lambda fmt: f"{template_type}.{fmt}.jinja"
-    plain = render_template(t("txt"), **incoming)
-    html = render_template(t("html"), **incoming)
-
-    if plain and html:
-        json = {'text':plain,'html':html, **hookshot_params}
-        print('Sending hookshot: ' + str(json))
-        r = requests.post(url + hook, json=json)
-    else:
-        print('Invalid format, sending incoming as str.')
-        r = requests.post(url + hook, json={'text':'Invalid format: ' + str(incoming)})
+    sanitized = grafana_sanitize_incoming(incoming)
 
-    return {"ok":True}
+    plain = render_template(f"{template_type}.txt.jinja", **sanitized)
+    html = render_template(f"{template_type}.html.jinja", **sanitized)
 
+    json = {"text": plain, "html": html, **hookshot_params}
+    print("Sending hookshot: " + str(json))
+    r = requests.post(url + hook, json=json)
 
-if __name__ == "__main__":
-    app.run(host="0.0.0.0", port=9080, debug=True)
+    return {"ok": True}
diff --git a/config.py b/config.py
index 000562e4b7a57913ebfd65c3e1d87fc397aadb80..525292e4fff501423607f212307b2ad3be56e1ec 100644
--- a/config.py
+++ b/config.py
@@ -1,6 +1,44 @@
-url = 'https://hookshot.mbot.ethz.ch/webhook/'
+url = "https://hookshot.mbot.ethz.ch/webhook/"
 flask_params = {"template_folder": "template"}
 hookshot_params = {"version": "v2", "msgtype": "m.notice"}
 
-msg_templates = ("default", "concise", "ultra_concise")
-msg_template_default = "ultra_concise"
+
+reserved_label_keys = ["grafana_folder", "alertname"]
+special_annotation_keys = ["description", "summary"]
+
+reserved_labels = {k: "" for k in reserved_label_keys}
+special_annotations = {k: "" for k in special_annotation_keys}
+
+incoming_default = {
+    "receiver": "",
+    "status": "",
+    "orgId": 0,
+    "alerts": [],
+    "groupLabels": {},
+    "commonLabels": {},
+    "commonAnnotatons": {},
+    "externalUrl": "",
+    "version": "",
+    "groupKey": "",
+    "commonStartsAtParsed": "",  # computed
+}
+
+alert_default = {
+    "status": "",
+    "labels": {},
+    "annotations": {},
+    "startsAt": "",
+    "endsAt": "",
+    "values": {},
+    "generatorURL": "",
+    "fingerprint": "",
+    "silenceURL": "",
+    "imageURL": "",
+    "uniqueLabels": "",  # computed
+    "uniqueAnnotations": "",  # computed
+    "startsAtParsed": "",  # computed
+    "endsAtParsed": "",  # computed
+}
+
+msg_templates = ("default", "oneliner", "detailed", "detailed_table")
+msg_template_default = "default"
diff --git a/sanitize.py b/sanitize.py
new file mode 100644
index 0000000000000000000000000000000000000000..11abfafee310543f8b06b6ee01a306af68233490
--- /dev/null
+++ b/sanitize.py
@@ -0,0 +1,99 @@
+from datetime import datetime
+from typing import Dict, List, Tuple
+
+from config import alert_default, incoming_default, reserved_labels, special_annotations
+
+
+def get_unique_dict(source: Dict, common: Tuple[str]) -> Dict:
+    """Filters out entries present in common keys"""
+    return {k: v for k, v in source.items() if k not in common}
+
+
+def get_common_time(alerts: List[Dict], key: str) -> str | None:
+    """Return formatted timestamp if is common across alert instances"""
+    unique_starts_at = {a[key] for a in alerts}
+    if len(unique_starts_at) == 1:
+        return unique_starts_at.pop()
+
+
+def merge_with_default_dict(source_raw: Dict, default: Dict) -> Dict:
+    """Merges source dict with default dict. Handles source_raw being None"""
+    source = source_raw if isinstance(source_raw, dict) else dict()
+    return default | source
+
+
+def grafana_sanitize_incoming(incoming: Dict) -> Dict:
+    sanitized = merge_with_default_dict(incoming, incoming_default)
+
+    sanitized["commonLabels"] = merge_with_default_dict(
+        sanitized["commonLabels"], reserved_labels
+    )
+
+    sanitized["commonAnnotations"] = merge_with_default_dict(
+        sanitized["commonAnnotations"], special_annotations
+    )
+
+    sanitized["alerts"] = [
+        grafana_sanitize_alert(
+            alert,
+            sanitized["commonLabels"].keys(),
+            sanitized["commonAnnotations"].keys(),
+        )
+        for alert in incoming.get("alerts", [])
+    ]
+
+    firing_alerts = [a for a in sanitized["alerts"] if a["status"] == "firing"]
+    sanitized["numberOfFiring"] = len(firing_alerts)
+
+    for special_key in special_annotations.keys():
+        sanitized[special_key] = sanitized["commonAnnotations"].get(special_key, "")
+
+    sanitized["normalAnnotations"] = {
+        k: v
+        for k, v in sanitized["commonAnnotations"].items()
+        if k not in special_annotations
+    }
+    sanitized["normalLabels"] = {
+        k: v for k, v in sanitized["commonLabels"].items() if k not in reserved_labels
+    }
+
+    sanitized["commonStartsAtParsed"] = get_common_time(
+        sanitized["alerts"], "startsAtParsed"
+    )
+    sanitized["commonEndsAtParsed"] = get_common_time(
+        sanitized["alerts"], "endsAtParsed"
+    )
+
+    return sanitized
+
+
+def grafana_sanitize_alert(
+    alert: Dict, common_label_keys: Tuple[str], common_annotation_keys: Tuple[str]
+) -> Dict:
+    sanitized = alert_default | alert
+
+    sanitized["values"] = merge_with_default_dict(sanitized["values"], dict())
+
+    sanitized["uniqueLabels"] = get_unique_dict(sanitized["labels"], common_label_keys)
+    sanitized["uniqueAnnotations"] = get_unique_dict(
+        sanitized["annotations"], common_annotation_keys
+    )
+
+    sanitized["startsAtParsed"] = format_dt(sanitized.get("startsAt", ""))
+    sanitized["endsAtParsed"] = format_dt(sanitized.get("endsAt", ""))
+
+    return sanitized
+
+
+def format_dt(dt_str: str) -> str:
+    """
+    Returns empty string if error or is year 1 e.g. zero value of golang time.Time
+    see https://pkg.go.dev/time#Time
+    """
+    try:
+        dt = datetime.fromisoformat(dt_str)
+        if dt.year < 2:
+            return ""
+        return dt.strftime("%Y-%m-%d %H:%M:%S %z")
+    except ValueError:
+        return ""
diff --git a/template/concise.html.jinja b/template/concise.html.jinja
deleted file mode 100644
index 3c423ed8ebf967ef599cdf65846f59ae51b5beef..0000000000000000000000000000000000000000
--- a/template/concise.html.jinja
+++ /dev/null
@@ -1,16 +0,0 @@
-{% if alerts | length %}
-{% for alert in alerts %}
-<b><a href="{{ alert.generatorURL }}">[{{ alert.status }}] {{ alert.labels.alertname }}</a>:</b>
-{% if alert.labels %}
-{% for key, value in alert.labels.items() %}
-{{ key }}={{ value }} {{ ", " if not loop.last else " " }}
-{% endfor %}
-{% endif %}
-(<a href="{{ alert.silenceURL }}">Silence</a>)<br>
-{% if alert.valuesForJinja %}
-{% for key, value in alert.valuesForJinja.items() %}
-<b>{{ key }}:</b> {{ value }}<br>
-{% endfor %}
-{% endif %}
-{% endfor %}
-{% endif %}
diff --git a/template/concise.txt.jinja b/template/concise.txt.jinja
deleted file mode 100644
index 1451d1460cd1d05a12e29b13ceae29f2b5a3917a..0000000000000000000000000000000000000000
--- a/template/concise.txt.jinja
+++ /dev/null
@@ -1,8 +0,0 @@
-{% if alerts | length %}{% for alert in alerts %}
-[{{ alert.status }}] {{ alert.labels.alertname }}:
-Alert: {{ alert.generatorURL }}
-Silence: {{ alert.silenceURL }}
-{% if alert.labels %}{% for key, value in alert.labels.items() %}{{ key }}={{ value }}, {% endfor %}{% endif %}
-{% if alert.valuesForJinja %}{% for key, value in alert.valuesForJinja.items() %}{{ key }}: {{ value }}, {% endfor %}{% endif %}
-___
-{% endfor %}{% endif %}
diff --git a/template/default.html.jinja b/template/default.html.jinja
index 0d2fe3945c74680df5479d9fb5029717fc1edeb2..407e50003bdea3e8393a11abd587e6e0b9be4d2a 100644
--- a/template/default.html.jinja
+++ b/template/default.html.jinja
@@ -1,34 +1,14 @@
-<b>{{ status | upper }}:{{ noFiring }} ({{ commonLabels.values() | join(" ") }})</b>
-<p><a href="{{ externalUrl }}?orgId={{ orgId }}">Grafana Instance</a></p>
-{% if alerts | length %}{% for alert in alerts %}
-<p><b>{{ alert.labels.alertname }}</b> (Status: {{ alert.status }}):
-    <a href="{{ alert.generatorURL }}">View</a>, <a href="{{ alert.silenceURL }}">Silence</a>
-</p>
-
-{% if alert.valuesForJinja %}
-<b>Values</b>
-<table>
-    {% for key, value in alert.valuesForJinja.items() %}
-    <tr>
-        <td>{{ key }}</td>
-        <td>{{ value }}</td>
-    </tr>
-    {% endfor %}
-</table>
-{% endif %}
-
-{% if alert.labels %}
-<b>Labels</b>
-<table>
-    {% for key, value in alert.labels.items() %}
-    <tr>
-        <td>{{ key }}</td>
-        <td>{{ value }}</td>
-    </tr>
-    {% endfor %}
-</table>
-{% endif %}
-<p><i>(StartsAt: {{alert.startsAtParsed }}{% if alert.endsAtParsed %}, EndsAt: {{alert.endsAtParsed }}{% endif %})</i>
-</p>
-<br>
-{% endfor %}{% endif %}
\ No newline at end of file
+{%- if alerts | length %}{% for alert in alerts -%}
+<b><a href="{{ alert['generatorURL'] }}">[{{ alert['status'] }}] {{ alert['labels']['alertname'] }}</a></b>
+{%- for key, value in alert['uniqueLabels'].items() -%}
+{%- if key != 'alertname' %}
+{{- " " + key }}={{ value }} {{ "," if not loop.last }}
+{%- endif %}
+{%- endfor -%}
+&#32;(<a href="{{ alert['silenceURL'] }}">Silence</a>)<br>
+{%- if alert['values'] %}
+{%- for key, value in alert['values'].items() -%}
+<b>{{ key }}:</b> {{ value }}<br>
+{%- endfor %}
+{%- endif %}
+{%- endfor %}{% endif %}
\ No newline at end of file
diff --git a/template/default.txt.jinja b/template/default.txt.jinja
index c5ae8c593278be08284f2ef76d33ba5698e7be5d..26b827c4d6fa15deb77614a586b4673155af1110 100644
--- a/template/default.txt.jinja
+++ b/template/default.txt.jinja
@@ -1,26 +1,10 @@
-{{ status | upper }}:{{ noFiring }} ({{ commonLabels.values() | join(" ") }})
-Grafana: {{ externalUrl }}?orgId={{ orgId }}
-
-{% if alerts | length %}
-{% for alert in alerts %}
----
-Name: {{ alert.labels.alertname }}
-Status: {{ alert.status }}
-View alert: {{ alert.generatorURL }}
-Silence alert: {{ alert.silenceURL }}
-
-{% if alert.Values %}
-Values:
-{% for key, value in alert.Values.items() %}
-{{ key }}: {{ value }}
-{% endfor %}
-{% endif %}
-
-{% if alert.labels %}
-Labels
-{% for key, value in alert.labels.items() %}
-{{ key }}: {{ value }}
-{% endfor %}
-{% endif %}
-{% endfor %}
-{% endif %}
+{%- for alert in alerts -%}
+[{{ alert['status'] }}] {{ alert['labels']['alertname'] -}} 
+{%- for key, value in alert['uniqueLabels'].items() %}
+{{- " " + key }}={{value}}
+{%- endfor %}:
+{%- for key, value in alert['values'].items() %}
+{{- "\n" + key }} = {{ value }}
+{%- endfor -%}
+{{- "\n" if not loop.last }}
+{%- endfor %}
\ No newline at end of file
diff --git a/template/detailed.html.jinja b/template/detailed.html.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..80df0e2bf5f1feb61cc9f1ba85d7ba2dd74c392a
--- /dev/null
+++ b/template/detailed.html.jinja
@@ -0,0 +1,83 @@
+{% for alert in alerts -%}
+<p><b><a href="{{ alert['generatorURL'] }}">[{{ alert['status'] }}] {{ alert['labels']['alertname'] }}</a></b> (<a href="{{ alert['silenceURL'] }}">Silence</a>):
+{%- if alert['imageURL'] -%}
+, <a href="{{ alert['imageURL'] }}">Image</a>
+{%- endif -%}
+
+<br>
+{%- for key, value in alert['values'].items() -%}
+{{- "<br>" if not loop.first }}
+<b>{{ key }}:</b> {{ value }}
+{%- endfor -%}
+
+<i>
+
+{%- if alert['uniqueLabels'] -%}
+<br>Labels:
+{{- "<br>" if (alert['uniqueLabels'] | length) > 1 else "&#32;" }}
+{%- for key, value in alert['uniqueLabels'].items() %}
+{{- "<br>" if not loop.first }}
+{{- "&emsp;" if (alert['uniqueLabels'] | length) > 1 }}
+{{ key }} = {{ value }}<br>
+{%- endfor %}{% endif %}
+
+{%- if alert['uniqueAnnotations'] -%}
+<br>Annotations:
+{{- "<br>" if (alert['uniqueAnnotations'] | length) > 1 else "&#32;" }}
+{%- for key, value in alert['uniqueAnnotations'].items() %}
+{{- "<br>" if not loop.first }}
+{{- "&emsp;" if (alert['uniqueAnnotations'] | length) > 1 }}
+{{- key }} = {{ value }}<br>
+{%- endfor %}{% endif %}
+
+{%- if not commonStartsAtParsed %}
+<i>(StartsAt: {{alert['startsAtParsed'] }}{% if alert['endsAtParsed'] %}, EndsAt: {{alert['endsAtParsed'] }}{% endif %})</i><br>
+{% endif -%}
+
+</i></p>{% endfor %}
+
+<p><b>Metadata:</b><br>
+<b>Grafana Folder:</b> {{ commonLabels['grafana_folder'] }}<br>
+{%- if summary -%}
+<b>Summary:</b> {{ summary }}<br>{% endif %}
+
+{%- if description -%}
+<b>Description:</b> {{ description }}<br>{% endif %}
+
+{%- if groupLabels -%}
+<b>Grouped By:</b>
+{{- "<br>" if (groupLabels | length) > 1 else "&#32;" }}
+{%- for key, value in groupLabels.items() %}
+{{- "&emsp;" if (groupLabels | length) > 1 }}
+{{- key }} = {{ value }}<br>
+{%- endfor %}{% endif %}
+
+{%- if normalLabels -%}
+<b>Common Labels:</b>
+{{- "<br>" if (normalLabels | length) > 1 else "&#32;" }}
+{%- for key, value in normalLabels.items() %}
+{{- "&emsp;" if (normalLabels | length) > 1 }}
+{{- key }} = {{ value }}<br>
+{%- endfor %}{% endif %}
+
+{%- if normalAnnotations -%}
+<b>Common Annotations:</b>
+{{- "<br>" if (normalAnnotations | length) > 1 else "&#32;" }}
+{%- for key, value in normalAnnotations.items() %}
+{{- "&emsp;" if (normalAnnotations | length) > 1 }}
+{{- key }} = {{ value }}<br>
+{%- endfor %}{% endif %}
+
+{%- if commonStartsAtParsed -%}
+<b>Starts At:</b> {{ commonStartsAtParsed }}
+{%- endif -%}
+{%- if commonEndsAtParsed -%}
+<br><b>Ends At:</b> {{ commonEndsAtParsed }}
+{%- endif -%}
+</p>
+
+
+
+{%- if truncatedAlerts %}
+<p><b>Truncated Alerts:</b> {{ truncatedAlerts }}</p>
+{% endif %}
diff --git a/template/detailed.txt.jinja b/template/detailed.txt.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..d733e9a61ec41329a47481209d08e7d48d93e8a4
--- /dev/null
+++ b/template/detailed.txt.jinja
@@ -0,0 +1,67 @@
+{% for alert in alerts %}
+[{{ alert['status'] }}] {{ alert['labels']['alertname'] }}
+{% for key, value in alert['values'].items() %}
+{{ key }}: {{ value }}
+{% endfor %}
+
+{%- if alert['uniqueLabels'] %}
+Labels:
+{%- for key, value in alert['uniqueLabels'].items() %}
+{{- "," if not loop.first }}
+{{- " " + key }} = {{ value }}
+{% endfor %}{% endif %}
+
+{% if alert['uniqueAnnotations'] %}
+Annotations:
+{% for key, value in alert['uniqueAnnotations'].items() %}
+{{ key }} = {{ value }}
+{% endfor %}{% endif %}
+
+{%- if not commonStartsAtParsed %}
+StartsAt: {{alert['startsAtParsed'] }}{% if alert['endsAtParsed'] %}, EndsAt: {{alert['endsAtParsed'] }}
+{% endif %}
+{% endif %}{% endfor -%}
+
+Metadata:
+Grafana Folder: {{ commonLabels['grafana_folder'] }}
+{% if summary %}
+Summary: {{ summary }}
+{% endif -%}
+
+{% if description %}
+Description: {{ description }}
+{% endif -%}
+
+{% if groupLabels %}
+Grouped By:
+{{- "\n" if (groupLabels | length) > 1 else " " }}
+{%- for key, value in groupLabels.items() %}
+{{- key }} = {{ value }}
+{% endfor %}
+{% endif %}
+
+{%- if normalLabels %}
+Common Labels:
+{{- "\n" if (normalLabels | length) > 1 else " " }}
+{%- for key, value in normalLabels.items() %}
+{{- key }} = {{ value }}
+{%- endfor %}
+{% endif %}
+
+{%- if normalAnnotations %}
+Common Annotations:
+{% for key, value in normalAnnotations.items() %}
+{{ key }} = {{ value }}
+{% endfor %}
+{% endif %}
+
+{%- if commonStartsAtParsed -%}
+Starts At: {{ commonStartsAtParsed -}}
+{% endif -%}
+{% if commonEndsAtParsed -%}
+Ends At: {{ commonEndsAtParsed -}}
+{%- endif -%}
+
+{%- if truncatedAlerts %}
+Truncated Alerts: {{ truncatedAlerts }}
+{% endif -%}
\ No newline at end of file
diff --git a/template/detailed_table.html.jinja b/template/detailed_table.html.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..269c5b2b25654e33422dacc66c65aee94240a201
--- /dev/null
+++ b/template/detailed_table.html.jinja
@@ -0,0 +1,88 @@
+{% for alert in alerts -%}
+<p><b><a href="{{ alert['generatorURL'] }}">[{{ status}}] {{ alert['labels']['alertname'] }}</a></b>(<a href="{{ alert['silenceURL'] }}">Silence</a>):
+
+{% if alert['values'] %}
+<table>
+<caption>Values</caption>
+{% for key, value in alert['values'].items() %}
+<tr>
+<th>{{ key }}</th>
+<td>{{ value }}</td>
+</tr>
+{% endfor %}
+</table>
+{% else %}
+<br>
+{% endif %}
+
+{% if alert['uniqueLabels'] %}
+<table>
+<caption>Labels</caption>
+{% for key, value in alert['uniqueLabels'].items() %}
+<tr>
+<td>{{ key }}</td>
+<td>{{ value }}</td>
+</tr>
+{% endfor %}
+</table>
+{% endif %}
+
+{% if alert['uniqueAnnotations'] %}
+<b>Annotations</b>
+<table>
+    {% for key, value in alert['uniqueAnnotations'].items() %}
+    <tr>
+        <td>{{ key }}</td>
+        <td>{{ value }}</td>
+    </tr>
+    {% endfor %}
+</table>
+{% endif %}
+
+<i>(StartsAt: {{alert['startsAtParsed'] }}{% if alert['endsAtParsed'] %}, EndsAt: {{alert['endsAtParsed'] }}{% endif %})</i>
+</p>
+{% endfor %}
+
+<p><b>Metadata:</b></p>
+
+<table>
+<tr><td>Grafana Folder</td><td>{{ commonLabels['grafana_folder'] }}</td></tr>
+{%- if summary -%}<tr><td>Summary</td><td>{{ summary }}</td></tr>{% endif %}
+{%- if description -%}<tr><td>Description</td><td>{{ description }}</td>{% endif %}
+</table>
+
+{% if groupLabels %}
+<table>
+    <caption>Grouped By:</caption>
+    {% for key, value in groupLabels.items() %}
+    <tr>
+        <td>{{ key }}</td>
+        <td>{{ value }}</td>
+    </tr>
+    {% endfor %}
+</table>
+{% endif %}
+
+{% if normalLabels %}
+<table>
+    <caption>Common Labels</caption>
+    {% for key, value in normalLabels.items() %}
+    <tr>
+        <td>{{ key }}</td>
+        <td>{{ value }}</td>
+    </tr>
+    {% endfor %}
+</table>
+{% endif %}
+
+{% if normalAnnotations %}
+<table>
+    <caption>Common Annotations</caption>
+    {% for key, value in normalAnnotations.items() %}
+    <tr>
+        <td>{{ key }}</td>
+        <td>{{ value }}</td>
+    </tr>
+    {% endfor %}
+</table>
+{% endif %}
\ No newline at end of file
diff --git a/template/detailed_table.txt.jinja b/template/detailed_table.txt.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..b5a0f3af59c221954ecc49b8b314d222219ffaf1
--- /dev/null
+++ b/template/detailed_table.txt.jinja
@@ -0,0 +1 @@
+{% include 'detailed.txt.jinja' %}
\ No newline at end of file
diff --git a/template/oneliner.html.jinja b/template/oneliner.html.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..fb0d5c7491cb586d36e5333d154c53994e54838a
--- /dev/null
+++ b/template/oneliner.html.jinja
@@ -0,0 +1,12 @@
+{% if alerts | length %}{% for alert in alerts -%}
+<b><a href="{{ alert['generatorURL'] }}">[{{ alert.status }}]
+{{- "&#32;" + alert['labels']['alertname'] }}</a></b> 
+{%- for key, value in alert['uniqueLabels'].items() %}
+{{- " " + key }}={{value}}
+{%- endfor -%}
+&#32;(<a href="{{ alert['silenceURL'] }}">Silence</a>)&#32;
+{%- for key, value in alert['values'].items() %}
+{{- ",&#32;" if not loop.first -}}
+<b>{{ key }}:</b> {{ value }}
+{%- endfor %}<br>
+{%- endfor -%}{% endif -%}
diff --git a/template/oneliner.txt.jinja b/template/oneliner.txt.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..e3f4e5a7a8b58f3734ce19f0883cf5c201b8ce2f
--- /dev/null
+++ b/template/oneliner.txt.jinja
@@ -0,0 +1,11 @@
+{%- for alert in alerts -%}
+[{{ alert['status'] }}] {{ alert['labels']['alertname'] -}} 
+{%- for key, value in alert['uniqueLabels'].items() %}
+{{- " " + key }}={{value}}
+{%- endfor %}:
+{%- for key, value in alert['values'].items() %}
+{{- "," if not loop.first }}
+{{- " " + key }} = {{ value }}
+{%- endfor -%}
+{{- "\n" if not loop.last }}
+{%- endfor %}
\ No newline at end of file
diff --git a/template/slack.html.jinja b/template/slack.html.jinja
index bfe10b05cb95ba0de489a1ef12d7e64cf06d003f..dc085a614aa8b0ffcb1283ccabd48267f9c34c0b 100644
--- a/template/slack.html.jinja
+++ b/template/slack.html.jinja
@@ -13,7 +13,7 @@
 {% endif %}
 
 {% for field in fields %}
-<b>{{ field.title }}:</b> {{ value}}<br/>
+<b>{{ field['title'] }}:</b> {{ value}}<br/>
 {% endfor %}
 
 {% if footer %}
diff --git a/template/slack.txt.jinja b/template/slack.txt.jinja
index 93a2b4bf37a96c5ff03cdf91312f8e81dab6d61f..cbb9fa26613cc533e52a24be4b2d4268a62f7ad9 100644
--- a/template/slack.txt.jinja
+++ b/template/slack.txt.jinja
@@ -5,7 +5,7 @@
 {% if text %}{{ text }}{% endif %}
 
 {% for field in fields %}
-{{ field.title }}: {{ value}}
+{{ field['title'] }}: {{ value}}
 {% endfor %}
 
 {{ footer}}
diff --git a/template/ultra_concise.html.jinja b/template/ultra_concise.html.jinja
deleted file mode 100644
index fa2d51d5a798dabc6432d9248a2fbe65812f556d..0000000000000000000000000000000000000000
--- a/template/ultra_concise.html.jinja
+++ /dev/null
@@ -1,18 +0,0 @@
-{% if alerts | length %}
-{% for alert in alerts %}
-{{ "<br>" if not loop.first }}
-<b><a href="{{ alert.generatorURL }}">[{{ alert.status }}] {{ alert.labels.alertname }}</a></b> 
-(<a href="{{ alert.silenceURL }}">Silence</a>)<b>:</b>
-{% if alert.labels %}
-{% for key, value in alert.labels.items() %}
-{{ key }}={{ value }} {{- ", " if not loop.last else " " }}
-{% endfor %}
-{% endif %}
-<br>
-{% if alert.valuesForJinja %}
-{% for key, value in alert.valuesForJinja.items() %}
-<b>{{ key }}:</b> {{ value }}{{ ", " if not loop.last else " " }}
-{% endfor %}
-{% endif %}
-{% endfor %}
-{% endif %}
diff --git a/template/ultra_concise.txt.jinja b/template/ultra_concise.txt.jinja
deleted file mode 100644
index 1451d1460cd1d05a12e29b13ceae29f2b5a3917a..0000000000000000000000000000000000000000
--- a/template/ultra_concise.txt.jinja
+++ /dev/null
@@ -1,8 +0,0 @@
-{% if alerts | length %}{% for alert in alerts %}
-[{{ alert.status }}] {{ alert.labels.alertname }}:
-Alert: {{ alert.generatorURL }}
-Silence: {{ alert.silenceURL }}
-{% if alert.labels %}{% for key, value in alert.labels.items() %}{{ key }}={{ value }}, {% endfor %}{% endif %}
-{% if alert.valuesForJinja %}{% for key, value in alert.valuesForJinja.items() %}{{ key }}: {{ value }}, {% endfor %}{% endif %}
-___
-{% endfor %}{% endif %}
diff --git a/templates.md b/templates.md
new file mode 100644
index 0000000000000000000000000000000000000000..a41f3bbd545770f5aa51d7fdd7f7cf3c59801bf4
--- /dev/null
+++ b/templates.md
@@ -0,0 +1,186 @@
+# Templates
+
+## `default`
+
+![default template html](_screenshots/templates/default.html.png)
+
+```txt
+[firing] humidity:
+humidity_latest = 51.479
+```
+
+```txt
+[firing] temp:
+temp_latest = 26.262
+```
+
+```txt
+[firing] wetbulb:
+H = 52.158
+T = 26.456
+WB = 25.023772144
+```
+
+<details><summary>cpu_usage</summary>
+
+![default template html cpu usage](_screenshots/templates/default-cpu_usage.html.png)
+
+```txt
+[firing] cpu_usage cpu=0:
+B = 2.4578
+[firing] cpu_usage cpu=1:
+B = 2.4058
+[firing] cpu_usage cpu=2:
+B = 2.48043
+[firing] cpu_usage cpu=3:
+B = 2.41827
+[firing] cpu_usage cpu=4:
+B = 2.2762
+[firing] cpu_usage cpu=5:
+B = 9.42877
+[firing] cpu_usage cpu=6:
+B = 2.36813
+[firing] cpu_usage cpu=7:
+B = 2.40608
+```
+
+</details>
+
+## `oneliner`
+
+![oneliner template html](_screenshots/templates/oneliner.html.png)
+
+```txt
+[firing] humidity: humidity_latest = 50.015
+```
+
+```txt
+[firing] temp: temp_latest = 26.198
+```
+
+```txt
+[firing] wetbulb: H = 50.045, T = 26.155, WB = 24.478525925
+```
+
+<details><summary>cpu_usage</summary>
+
+![oneliner template html cpu usage](_screenshots/templates/oneliner-cpu_usage.html.png)
+
+```txt
+[firing] cpu_usage cpu=0: B = 2.46086
+[firing] cpu_usage cpu=1: B = 2.40885
+[firing] cpu_usage cpu=2: B = 2.48347
+[firing] cpu_usage cpu=3: B = 2.4212
+[firing] cpu_usage cpu=4: B = 2.27917
+[firing] cpu_usage cpu=5: B = 9.42941
+[firing] cpu_usage cpu=6: B = 2.37108
+[firing] cpu_usage cpu=7: B = 2.40908
+```
+
+</details>
+
+## `detailed`
+
+![detailed template html ](_screenshots/templates/detailed.html.png)
+
+```txt
+[firing] humidity
+humidity_latest: 52.097
+
+Metadata:
+Grafana Folder: capybara
+Grouped By: grafana_folder = capybara
+Common Labels: test_label = testCommon Annotations:
+test_annotation = test
+Starts At: 2024-05-22 11:08:10 +0200
+```
+
+```txt
+[firing] temp
+temp_latest: 26.155
+
+Metadata:
+Grafana Folder: capybara
+Grouped By: grafana_folder = capybara
+Starts At: 2024-05-10 14:42:10 +0200
+```
+
+```txt
+[firing] wetbulb
+H: 52.097
+T: 26.155
+WB: 24.758552105000003
+
+Metadata:
+Grafana Folder: capybara
+Summary: calculates the approximated wet bulb temperature
+Description: https://schweizer-fn.de/lueftung/feuchte/feuchte.php
+Grouped By: grafana_folder = capybara
+Starts At: 2024-05-15 17:52:10 +0200
+```
+
+<details><summary>cpu_usage</summary>
+
+![detailed template html cpu usage](_screenshots/templates/detailed-cpu_usage.html.png)
+
+```txt
+[firing] cpu_usage
+B: 2.46297
+Labels: cpu = 0
+
+[firing] cpu_usage
+B: 2.41088
+Labels: cpu = 1
+
+[firing] cpu_usage
+B: 2.48554
+Labels: cpu = 2
+
+[firing] cpu_usage
+B: 2.42328
+Labels: cpu = 3
+
+[firing] cpu_usage
+B: 2.28143
+Labels: cpu = 4
+
+[firing] cpu_usage
+B: 9.42974
+Labels: cpu = 5
+
+[firing] cpu_usage
+B: 2.37328
+Labels: cpu = 6
+
+[firing] cpu_usage
+B: 2.41123
+Labels: cpu = 7
+
+Metadata:
+Grafana Folder: capybara
+Summary: CPU usage by core
+Description: Does what it says on the tin
+Grouped By: grafana_folder = capybara
+Common Annotations:
+customLabel = Test Test Test
+runbook_url = https://web.archive.org/
+Starts At: 2024-05-25 06:25:10 +0200
+```
+
+</details>
+
+## `detailed_table`
+
+Plaintext version is the same as `detailed`.
+
+![detailed table template temp](_screenshots/templates/detailed_table-temp.html.png)  
+![detailed table template humidity](_screenshots/templates/detailed_table-humidity.html.png)  
+![detailed table template wetbulb](_screenshots/templates/detailed_table-wetbulb.html.png)
+
+<details><summary>cpu_usage</summary>
+
+![detailed table template cpu_usage0](_screenshots/templates/detailed_table-cpu_usage0.html.png)  
+(...)  
+![detailed table template cpu_usage1](_screenshots/templates/detailed_table-cpu_usage1.html.png)
+
+</details>
diff --git a/validate.py b/validate.py
deleted file mode 100644
index c7fe8f2822a08187ddcbd5f7e0ff52dd0493f109..0000000000000000000000000000000000000000
--- a/validate.py
+++ /dev/null
@@ -1,85 +0,0 @@
-from datetime import datetime
-from typing import Dict
-
-# https://grafana.com/docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/
-
-
-def validate_str_entry(d: Dict, k: str):
-    """Empty string if not exists"""
-    d[k] = d.get(k, "")
-
-
-def validate_number_entry(d: Dict, k: str):
-    """0 if not exists"""
-    d[k] = d.get(k, 0)
-
-
-def validate_dict_entry(d: Dict, k: str):
-    """Empty dict if not exists"""
-    entry = d.get(k, None)
-    if not isinstance(entry, dict):
-        entry = dict()
-    d[k] = entry
-
-
-def grafana_validate_incoming(incoming: Dict):
-    """
-    Makes sure the necessary keys are present in the incoming dict so it can be parsed by jinja
-    """
-    string_keys = ("receiver", "status", "externalUrl", "version", "groupKey")
-    for key in string_keys:
-        validate_str_entry(incoming, key)
-
-    validate_number_entry(incoming, "orgId")
-    validate_number_entry(incoming, "truncatedAlerts")
-
-    validate_dict_entry(incoming, "groupLabels")
-    validate_dict_entry(incoming, "commonLabels")
-    validate_dict_entry(incoming, "commonAnnotations")
-
-    incoming["alerts"] = alerts = incoming.get("alerts", [])
-
-    for alert in alerts:  # Handle non dict values?
-        grafana_validate_alert(alert)
-
-    l = lambda a: a.get("status", "") == "firing"
-    incoming["no_firing"] = len(tuple(filter(l, alerts)))
-
-
-def grafana_validate_alert(alert: Dict):
-
-    string_keys = (
-        "status",
-        "startsAt",
-        "endsAt",
-        "generatorURL",
-        "fingerprint",
-        "silenceURL",
-        "imageURL",
-    )
-    for key in string_keys:
-        validate_str_entry(alert, key)
-
-    validate_dict_entry(alert, "labels")
-    validate_dict_entry(alert, "annotations")
-    validate_dict_entry(alert, "values")
-
-    # fixes "values" being shadowed in jinja
-    alert["valuesForJinja"] = alert.get("values", None)
-
-    alert["startsAtParsed"] = format_dt(alert.get("startsAt", ""))
-    alert["endsAtParsed"] = format_dt(alert.get("endsAt", ""))
-
-
-def format_dt(dt_str: str) -> str:
-    """
-    Returns empty string if error or is year 1 e.g. zero value of golang time.Time
-    see https://pkg.go.dev/time#Time
-    """
-    try:
-        dt = datetime.fromisoformat(dt_str)
-        if dt.year < 2:
-            return ""
-        return dt.strftime("%Y-%m-%d %H:%M:%S %z")
-    except ValueError:
-        return ""