diff --git a/parsers/grafana.php b/parsers/grafana.php
new file mode 100644
index 0000000..5e1e047
--- /dev/null
+++ b/parsers/grafana.php
@@ -0,0 +1,422 @@
+
+
Alert Summary
+
+HTML;
+ }
+
+ if ($externalURL) {
+ $externalURLSafe = htmlspecialchars($externalURL, ENT_QUOTES, 'UTF-8');
+ $alertSummary .= <<
+ Alertmanager URL
+
+
+HTML;
+ }
+
+ $alertSummary .= <<
+
+HTML;
+
+ // Build common labels section
+ $labelsContent = '';
+ if (!empty($groupLabels) || !empty($commonLabels)) {
+ $labelsContent .= 'Labels
';
+
+ if (!empty($groupLabels)) {
+ $labelsContent .= '
Group Labels
+HTML;
+ }
+ $labelsContent .= '
';
+ }
+
+ if (!empty($commonLabels)) {
+ $labelsContent .= 'Common Labels
+HTML;
+ }
+ $labelsContent .= '';
+ }
+
+ $labelsContent .= '';
+ }
+
+ // Build common annotations section
+ $annotationsContent = '';
+ if (!empty($commonAnnotations)) {
+ $annotationsContent .= 'Common Annotations
+HTML;
+ }
+ $annotationsContent .= '
';
+ }
+
+ // Build individual alerts section
+ $alertsContent = '';
+ if (!empty($alerts)) {
+ $alertsContent .= 'Alerts
';
+
+ foreach ($alerts as $index => $alert) {
+ $alertStatus = $alert['status'] ?? 'unknown';
+ $alertLabels = $alert['labels'] ?? [];
+ $alertAnnotations = $alert['annotations'] ?? [];
+ $startsAt = $alert['startsAt'] ?? null;
+ $endsAt = $alert['endsAt'] ?? null;
+ $generatorURL = $alert['generatorURL'] ?? null;
+ $fingerprint = $alert['fingerprint'] ?? null;
+
+ $alertStatusSafe = htmlspecialchars($alertStatus, ENT_QUOTES, 'UTF-8');
+ $alertNumber = $index + 1;
+
+ // Choose alert icon
+ $alertIcon = '🔔';
+ $alertColor = '#666666';
+ if ($alertStatus === 'firing') {
+ $alertIcon = '🔥';
+ $alertColor = '#dc3545';
+ } elseif ($alertStatus === 'resolved') {
+ $alertIcon = '✅';
+ $alertColor = '#28a745';
+ }
+
+ $alertsContent .= <<
+
$alertIcon Alert $alertNumber - $alertStatusSafe
+
+HTML;
+
+ // Add alert labels
+ if (!empty($alertLabels)) {
+ $alertsContent .= '
Labels
+HTML;
+ }
+ $alertsContent .= '
';
+ }
+
+ // Add alert annotations
+ if (!empty($alertAnnotations)) {
+ $alertsContent .= '
Annotations
+HTML;
+ }
+ $alertsContent .= '
+HTML;
+ }
+
+ if ($endsAt) {
+ $endsAtSafe = htmlspecialchars($endsAt, ENT_QUOTES, 'UTF-8');
+ $alertsContent .= <<
+ Ended At
+ $endsAtSafe
+
+HTML;
+ }
+
+ if ($generatorURL) {
+ $generatorURLSafe = htmlspecialchars($generatorURL, ENT_QUOTES, 'UTF-8');
+ $alertsContent .= <<
+ Generator URL
+
+
+HTML;
+ }
+
+ if ($fingerprint) {
+ $fingerprintSafe = htmlspecialchars($fingerprint, ENT_QUOTES, 'UTF-8');
+ $alertsContent .= <<
+ Fingerprint
+ $fingerprintSafe
+
+HTML;
+ }
+
+ $alertsContent .= '';
+ }
+
+ $alertsContent .= '';
+ }
+
+ $html = <<
+
+
+
+
+ Grafana Alert - $statusSafe
+
+
+
+
+
+
+ $alertSummary
+
+ $labelsContent
+
+ $annotationsContent
+
+ $alertsContent
+
+
+
📋 Request Information
+
+
+
+
+
+
+
+HTML;
+
+ // Create subject line
+ $alertCountText = $alertCount === 1 ? '1 alert' : "$alertCount alerts";
+ $subject = "$statusIcon Grafana Alert: $statusSafe - $alertCountText";
+
+ return [
+ 'html' => $html,
+ 'subject' => $subject
+ ];
+}
diff --git a/parsers/json.php b/parsers/json.php
new file mode 100644
index 0000000..c10b1d9
--- /dev/null
+++ b/parsers/json.php
@@ -0,0 +1,315 @@
+
+
+
+
+
+ JSON Webhook Data
+
+
+
+
+
+
+
+
📋 Request Information
+
+
+
+
+
📦 JSON Data
+
+HTML;
+
+ // Process the JSON data
+ $html .= renderJsonData($payload);
+
+ $html .= <<
+
+
+
+
+
+HTML;
+
+ $subject = "📊 JSON Webhook - " . $metadata['date'];
+
+ return [
+ 'html' => $html,
+ 'subject' => $subject
+ ];
+}
+
+/**
+ * Recursively render JSON data as tables
+ * Sub-arrays are extracted into separate tables with path-based titles
+ *
+ * @param mixed $data The data to render
+ * @param string $path The current path (e.g., "Foo->Bar->Baz")
+ * @return string HTML representation
+ */
+function renderJsonData($data, $path = '') {
+ $html = '';
+
+ if (!is_array($data)) {
+ $safe = htmlspecialchars((string)$data, ENT_QUOTES, 'UTF-8');
+ return "
$safe
";
+ }
+
+ // Check if it's an indexed array (numeric keys in sequence)
+ $isIndexedArray = array_keys($data) === range(0, count($data) - 1);
+
+ if ($isIndexedArray && count($data) > 0) {
+ // It's an array of items
+ foreach ($data as $index => $item) {
+ $itemPath = $path ? $path . '->[' . $index . ']' : '[' . $index . ']';
+
+ if (is_array($item)) {
+ // Check if this item has any sub-arrays
+ $subArrays = [];
+ $simpleData = [];
+
+ foreach ($item as $key => $value) {
+ if (is_array($value)) {
+ $subArrays[$key] = $value;
+ } else {
+ $simpleData[$key] = $value;
+ }
+ }
+
+ // Render simple data for this item
+ if (!empty($simpleData)) {
+ if ($path) {
+ $html .= '
' . htmlspecialchars($itemPath, ENT_QUOTES, 'UTF-8') . '
';
+ }
+
+ $html .= '
';
+ $html .= '
Item ' . ($index + 1) . '
';
+ $html .= '
';
+ $html .= '
';
+ }
+
+ // Recursively render sub-arrays as separate tables
+ foreach ($subArrays as $key => $value) {
+ $subPath = $itemPath . '->' . $key;
+ $html .= renderJsonData($value, $subPath);
+ }
+ } else {
+ // Simple value in array
+ $valueSafe = htmlspecialchars((string)$item, ENT_QUOTES, 'UTF-8');
+ $html .= '
';
+ }
+ }
+ } else {
+ // It's an associative array (object-like)
+ // Separate simple values from arrays
+ $subArrays = [];
+ $simpleData = [];
+
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ $subArrays[$key] = $value;
+ } else {
+ $simpleData[$key] = $value;
+ }
+ }
+
+ // Render simple key-value pairs
+ if (!empty($simpleData)) {
+ if ($path) {
+ $html .= '
' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '
';
+ }
+
+ $html .= '
';
+ }
+
+ // Recursively render sub-arrays as separate tables with path titles
+ foreach ($subArrays as $key => $value) {
+ $subPath = $path ? $path . '->' . $key : $key;
+ $html .= renderJsonData($value, $subPath);
+ }
+ }
+
+ return $html;
+}