From 70db264f774e79c530ac3333e2118c501ddb1dff Mon Sep 17 00:00:00 2001 From: cschantz Date: Wed, 31 Dec 2025 19:20:49 -0500 Subject: [PATCH] Add intelligent failure categorization and analysis New DELIVERY FAILURE ANALYSIS section that categorizes bounces: - Recipient doesn't exist (invalid email addresses) - Mailbox full (quota exceeded) - Relay denied (not authorized to send) - Blocked/Spam filtered (IP/domain blacklisted) - DNS/Domain issues (domain not found, no MX records) - Connection failures (timeout, refused) - Other failures (uncategorized) Each category shows: - Count of failures - Clear explanation of the reason - Suggested solutions - Example email addresses affected Makes it easy to understand WHY emails are failing instead of showing cryptic log entries. --- modules/email/email-diagnostics.sh | 109 ++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/modules/email/email-diagnostics.sh b/modules/email/email-diagnostics.sh index e405a0e..b43ea03 100755 --- a/modules/email/email-diagnostics.sh +++ b/modules/email/email-diagnostics.sh @@ -507,25 +507,102 @@ if [ "$delivered" -gt 0 ]; then fi if [ "$bounced" -gt 0 ]; then - print_header "RECENT BOUNCES/FAILURES (last 5 from past $hours hours)" - echo "" - print_warning "PROOF - These emails failed delivery:" - echo "" - grep -i "$search_pattern" "$TEMP_MATCHES" | grep -v "authenticator failed\|Authentication failed\|saved mail to\|=>" | grep -i "550\|551\|552\|553\|554\|bounced\|Mail delivery failed\|** " | tail -5 | while read line; do - timestamp=$(echo "$line" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|[A-Z][a-z]{2} [0-9]+ [0-9]{2}:[0-9]{2}:[0-9]{2}' | head -1) - if [ -n "$timestamp" ]; then - echo -e " ${RED}[$timestamp]${NC} $line" - else - echo " $line" - fi - done + print_header "DELIVERY FAILURE ANALYSIS" echo "" - # Extract bounce reasons - print_header "COMMON BOUNCE REASONS" + # Get all bounce lines + TEMP_BOUNCES="/tmp/email_bounces_$$.txt" + grep -i "$search_pattern" "$TEMP_MATCHES" | grep -v "authenticator failed\|Authentication failed\|saved mail to\|=>" | grep -i "550\|551\|552\|553\|554\|bounced\|Mail delivery failed\|** " > "$TEMP_BOUNCES" 2>/dev/null + + # Categorize failures + recipient_unknown=$(grep -ci "user unknown\|No such user\|does not exist\|recipient rejected\|Recipient address rejected\|550.*User" "$TEMP_BOUNCES" 2>/dev/null || echo 0) + mailbox_full=$(grep -ci "mailbox.*full\|quota.*exceeded\|552\|insufficient.*space\|over.*quota" "$TEMP_BOUNCES" 2>/dev/null || echo 0) + relay_denied=$(grep -ci "relay.*denied\|relay.*not.*permitted\|relaying denied\|554.*relay" "$TEMP_BOUNCES" 2>/dev/null || echo 0) + blocked=$(grep -ci "blocked\|blacklist\|550.*spam\|554.*spam\|Policy rejection" "$TEMP_BOUNCES" 2>/dev/null || echo 0) + dns_failure=$(grep -ci "domain.*not.*found\|Host.*unknown\|Name.*not.*resolve\|MX.*not.*found" "$TEMP_BOUNCES" 2>/dev/null || echo 0) + connection_fail=$(grep -ci "timeout\|connection.*refused\|connection.*failed\|Network.*unreachable" "$TEMP_BOUNCES" 2>/dev/null || echo 0) + + print_info "Failure breakdown by reason:" echo "" - grep -i "$search_pattern" "$TEMP_MATCHES" | grep -v "authenticator failed\|Authentication failed\|saved mail to" | grep -i "550\|551\|bounced\|** " | grep -oE "550 [^(]*|551 [^(]*|User unknown|Mailbox.*full|Relay.*denied|No such user|does not exist" | sort | uniq -c | sort -rn | head -5 + + if [ "$recipient_unknown" -gt 0 ]; then + print_error " Recipient doesn't exist: $recipient_unknown emails" + echo " Reason: Email address is invalid or doesn't exist on recipient server" + grep -i "user unknown\|No such user\|does not exist\|recipient rejected\|550.*User" "$TEMP_BOUNCES" | head -2 | while read line; do + echo " Example: $(echo "$line" | grep -oE '[^ ]+@[^ ,]+' | head -1)" + done + echo "" + fi + + if [ "$mailbox_full" -gt 0 ]; then + print_warning " Mailbox full: $mailbox_full emails" + echo " Reason: Recipient's mailbox has exceeded storage quota" + grep -i "mailbox.*full\|quota.*exceeded\|552" "$TEMP_BOUNCES" | head -2 | while read line; do + echo " Example: $(echo "$line" | grep -oE '[^ ]+@[^ ,]+' | head -1)" + done + echo "" + fi + + if [ "$relay_denied" -gt 0 ]; then + print_error " Relay denied: $relay_denied emails" + echo " Reason: Server not authorized to send to this domain" + echo " Solution: Check if server IP needs to be whitelisted" + echo "" + fi + + if [ "$blocked" -gt 0 ]; then + print_error " Blocked/Spam filtered: $blocked emails" + echo " Reason: Sender IP or domain is blacklisted, or content flagged as spam" + echo " Solution: Check IP reputation, SPF/DKIM records" + echo "" + fi + + if [ "$dns_failure" -gt 0 ]; then + print_error " DNS/Domain issues: $dns_failure emails" + echo " Reason: Recipient domain doesn't exist or has no MX records" + grep -i "domain.*not.*found\|Host.*unknown" "$TEMP_BOUNCES" | head -2 | while read line; do + echo " Example: $(echo "$line" | grep -oE '[^ ]+@[^ ,]+' | head -1)" + done + echo "" + fi + + if [ "$connection_fail" -gt 0 ]; then + print_warning " Connection failures: $connection_fail emails" + echo " Reason: Could not connect to recipient mail server (temporary)" + echo " Solution: These usually resolve themselves, check if persistent" + echo "" + fi + + # Calculate uncategorized + total_categorized=$((recipient_unknown + mailbox_full + relay_denied + blocked + dns_failure + connection_fail)) + if [ "$total_categorized" -lt "$bounced" ]; then + other=$((bounced - total_categorized)) + print_warning " Other failures: $other emails" + echo " See detailed logs below for specific reasons" + echo "" + fi + + # Show recent failures for reference + print_header "RECENT FAILURE EXAMPLES (last 3)" echo "" + grep -i "$search_pattern" "$TEMP_MATCHES" | grep -v "authenticator failed\|Authentication failed\|saved mail to\|=>" | grep -i "550\|551\|552\|553\|554\|bounced\|Mail delivery failed\|** " | tail -3 | while read line; do + timestamp=$(echo "$line" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|[A-Z][a-z]{2} [0-9]+ [0-9]{2}:[0-9]{2}:[0-9]{2}' | head -1) + if [ -n "$timestamp" ]; then + echo -e " ${RED}[$timestamp]${NC}" + # Extract the key error message + error_msg=$(echo "$line" | grep -oE "550 [^<]*|551 [^<]*|552 [^<]*|User unknown|does not exist|Mailbox full|relay denied" | head -1) + if [ -n "$error_msg" ]; then + echo " Error: $error_msg" + fi + recipient=$(echo "$line" | grep -oE '[^ ]+@[^ ,<>]+' | grep -v "$search_pattern" | head -1) + if [ -n "$recipient" ]; then + echo " To: $recipient" + fi + fi + echo "" + done + + rm -f "$TEMP_BOUNCES" fi if [ "$rejected" -gt 0 ]; then @@ -655,4 +732,4 @@ print_info "Full log saved to: $REPORT_FILE" echo "" # Cleanup -rm -f "$TEMP_MATCHES" "$TEMP_AUTH" "$TEMP_ALL" +rm -f "$TEMP_MATCHES" "$TEMP_AUTH" "$TEMP_ALL" "$TEMP_BOUNCES"