#!/usr/bin/env python3 """ Mailpit → Gotify bridge webhook. Receives POSTs from Mailpit (MP_WEBHOOK_URL) when new mail arrives. Fetches the full message from the Mailpit API, extracts useful info, and forwards a summary to Gotify. """ import logging import os import json from typing import Any, Dict import requests from dotenv import load_dotenv from flask import Flask, jsonify, request # ------------------------------------------------------------------ # # Config & logging # ------------------------------------------------------------------ # load_dotenv() MAILPIT_API = os.getenv("MAILPIT_API", "http://localhost:8025") MAILPIT_TOKEN = os.getenv("MAILPIT_TOKEN", "") NTFY_URL = os.getenv("NTFY_URL", "") NTFY_TOKEN = os.getenv("NTFY_TOKEN", "") NTFY_TOPIC = os.getenv("NTFY_TOPIC", "") LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() logging.basicConfig( level=getattr(logging, LOG_LEVEL, logging.INFO), format="%(asctime)s [%(levelname)s] %(message)s", ) log = logging.getLogger("mailpit-hook") app = Flask(__name__) from utils.ntfy_api import NtfyNotifier # ------------------------------------------------------------------ # # Helpers # ------------------------------------------------------------------ # def get_mailpit_message(message_id: str) -> Dict[str, Any]: """Retrieve full message JSON from Mailpit REST API.""" url = f"{MAILPIT_API}/api/v1/message/{message_id}" headers = {} if MAILPIT_TOKEN: headers["Authorization"] = f"Bearer {MAILPIT_TOKEN}" resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() return resp.json() def get_mailpit_image_thumb(message_id: str, part_id: str) -> bytes: """Retrieve image thumbnail from Mailpit REST API as binary data.""" url = f"{MAILPIT_API}/api/v1/message/{message_id}/part/{part_id}/thumb" headers = {} if MAILPIT_TOKEN: headers["Authorization"] = f"Bearer {MAILPIT_TOKEN}" resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() return resp.content def get_mailpit_image(message_id: str, part_id: str) -> bytes: """Retrieve image thumbnail from Mailpit REST API as binary data.""" url = f"{MAILPIT_API}/api/v1/message/{message_id}/part/{part_id}" headers = {} if MAILPIT_TOKEN: headers["Authorization"] = f"Bearer {MAILPIT_TOKEN}" resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() return resp.content def send_notification(title: str, message: str, priority: int = 5, image_data: bytes = None, filename: str = "image.jpg") -> bool: """Send a notification to Ntfy, optionally with an image attachment.""" notify = NtfyNotifier(NTFY_URL, NTFY_TOKEN, NTFY_TOPIC) if image_data: result = notify.send_with_image( title=title, message=message, image_data=image_data, filename=filename, priority=priority ) else: result = notify.send(title=title, message=message, priority=priority) if not result: log.warning("Ntfy push failed") else: log.info("Ntfy push OK") return result # ------------------------------------------------------------------ # # Webhook route # ------------------------------------------------------------------ # @app.route("/hook", methods=["POST"]) def hook(): """ Mailpit sends JSON like: { "ID": "abcdef123", "MessageID": "<...>", "From": "camera@reolink.local", "Subject": "Motion Detected", ... } """ data = request.get_json(silent=True) or {} mail_msg_id = data.get("MessageID") msg_id = data.get("ID") if not msg_id: log.warning("Webhook received malformed payload: %s", data) return jsonify({"error": "missing ID"}), 400 log.info(f"Webhook triggered for message ID={msg_id} - Email MSG_ID={mail_msg_id}") result = handle_hook(msg_id) if result: return jsonify({"status": "ok"}), 200 else: return jsonify({"error":"Error sending webhook"}), 500 def handle_hook(msg_id: str): try: msg = get_mailpit_message(msg_id) subject = msg.get("Subject", "(no subject)") text = msg.get("Text", "") or msg.get("HTML", "") attachments = msg.get("Attachments") or [] # Build preview text preview = (text or "") if len(preview) > 200: preview = preview[:200] + "..." # Check for image attachments and fetch thumbnail image_data = None filename = "image.jpg" if attachments: first_attachment = attachments[0] part_id = first_attachment.get("PartID") content_type = first_attachment.get("ContentType", "") if part_id and content_type.startswith("image/"): try: # image_data = get_mailpit_image_thumb(msg_id, part_id) image_data = get_mailpit_image(msg_id, part_id) # Extract filename from attachment if available filename = first_attachment.get("FileName", "image.jpg") except Exception as e: log.warning("Failed to fetch image thumbnail: %s", e) send_notification(subject, preview, image_data=image_data, filename=filename) return True except Exception as e: log.exception("Error processing webhook: %s", e) return False # ------------------------------------------------------------------ # # Entry point # ------------------------------------------------------------------ # if __name__ == "__main__": app.run(host="0.0.0.0", port=8088)