Privacy Policy
Snippets index

  monitor_mail

Save into '/usr/local/bin/monitor_mail', then:

chmod +x /usr/local/bin/monitor_mail

#!/usr/bin/env python3
import time
import re
import argparse

# Strong styles for primary fields
STATUS_STYLES = {
    'sent':      '\033[97;42m',  # white on green
    'bounced':   '\033[97;41m',  # white on red
    'deferred':  '\033[30;44m',  # black on blue
    'expired':   '\033[97;45m',  # white on magenta
    'reject':    '\033[97;41m',  # white on red
    'discarded': '\033[30;47m',  # black on white
    'hold':      '\033[30;46m',  # black on cyan
    'error':     '\033[97;41m',  # white on red
}
DEFAULT_STYLE = '\033[30;43m'    # black on yellow
SECONDARY_STYLE = '\033[94m'     # light blue
RESET = '\033[0m'

def highlight_status_and_to(line, use_color=True):
    """
    Highlights:
    - 'status=...' and 'to=<...>' with background color
    - 'from=...', 'relay=...', 'message-id=...' with light foreground color
    Skips orig_to.
    """
    status_match = re.search(r'status=([a-zA-Z0-9_]+)', line)
    if not status_match:
        return None

    status_value = status_match.group(1)

    if use_color:
        strong_style = STATUS_STYLES.get(status_value, DEFAULT_STYLE)
        secondary_style = SECONDARY_STYLE
    else:
        strong_style = ''
        secondary_style = ''

    reset = RESET if use_color else ''

    # Primary highlights
    line = re.sub(
        r'status=([a-zA-Z0-9_]+)',
        lambda m: f"{strong_style}{m.group(0)}{reset}",
        line
    )
    line = re.sub(
        r'(?<!orig_)to=<[^>]+>',
        lambda m: f"{strong_style}{m.group(0)}{reset}",
        line
    )

    # Secondary highlights
    line = re.sub(
        r'from=<[^>]+>',
        lambda m: f"{secondary_style}{m.group(0)}{reset}",
        line
    )
    line = re.sub(
        r'relay=[^ ,]+',
        lambda m: f"{secondary_style}{m.group(0)}{reset}",
        line
    )
    line = re.sub(
        r'message-id=<[^>]+>',
        lambda m: f"{secondary_style}{m.group(0)}{reset}",
        line
    )

    return line

def tail_f(filepath, show_all=False, from_start=False, use_color=True, only_status=None):
    """
    Tail -f with optional filtering, coloring and status filtering.
    """
    with open(filepath, 'r') as f:
        if not from_start:
            f.seek(0, 2)
        while True:
            line = f.readline()
            if not line:
                time.sleep(0.1)
                continue
            line = line.rstrip()

            # Extract status value (if any)
            status_match = re.search(r'status=([a-zA-Z0-9_]+)', line)
            status_value = status_match.group(1) if status_match else None

            if only_status and status_value != only_status:
                continue  # Skip line if it doesn't match the --only-status filter

            highlighted = highlight_status_and_to(line, use_color=use_color)
            if highlighted:
                print(highlighted)
            elif show_all:
                print(line)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Tail and highlight Postfix log lines")
    parser.add_argument(
        "--show-all", action="store_true",
        help="Show all lines (not just those with 'status=...')"
    )
    parser.add_argument(
        "--from-start", action="store_true",
        help="Start reading from the beginning of the file instead of the end"
    )
    parser.add_argument(
        "--no-color", action="store_true",
        help="Disable all ANSI color output (monochrome)"
    )
    parser.add_argument(
        "--only-status", metavar="STATUS",
        help="Show only lines with a specific status (e.g. bounced, sent, deferred)"
    )

    args = parser.parse_args()

    # Default path to Postfix log
    log_file_path = "/var/log/mail.log"

    # Start tailing
    tail_f(
        filepath=log_file_path,
        show_all=args.show_all,
        from_start=args.from_start,
        use_color=not args.no_color,
        only_status=args.only_status
    )