How to redeliver ‘Undelivered Mail Returned to Sender’ in /var/spool/mail

email-bouncesemail-server

After changing the IP of a server that was configured as a satellite email system I missed to update mynetworks on the mail server to allow the sending of emails on behalf of the new IP address.

That's why lots of emails were written to /var/spool/mail/ in the last week with a soft bounce notification Undelivered Mail Returned to Sender until we noticed this problem and fixed the mailserver configuration.

Now we want to redeliver the bounced emails to the original receivers. This means to parse the files at /var/spool/mail/* and extract the original emails to send them with original headers again. How to do that?

Best Answer

I wrote a PHP script to parse a /var/spool/mail/ file to resend bounced emails with original headers. Please be aware to change the configuration in the first lines to your needs.

<?php
DEFINE('SIMPLIFIEDBOUNDARYCHECK', '/hostxx.mydomain.tld--');
DEFINE('TEST', true);
DEFINE('TESTRECEIVER', 'me@example.com');
$excludeReceiver = array('some@example.com');

function startsWith($haystack, $needle) {
  return $needle === "" || strpos($haystack, $needle) === 0;
}

function endsWith($haystack, $needle) {
  return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}
if(!isset($argv[1])) {
  die("resend mails: filename argument missing, e.g. /var/spool/mail/www-data");
}
$filename = $argv[1];

$handle = fopen($filename, "r");
if(!$handle)
  die("$filename could not be opened");

$state = 0;
$mail = NULL;
while(($line = fgets($handle)) !== false) {

  switch($state) {
    case 0:
      if(startsWith($line, 'Content-Description: Undelivered Message')) {
        $state = 1;
        $mail = new Mail();
      }
      break;
    case 1: // pre-header
      $trimmedLine = trim($line);
      if(empty($trimmedLine)) {
        $state = 2;
        break;
      }
      break;
    case 2: // header
      $trimmedLine = trim($line);
      if(empty($trimmedLine)) {
        $state = 3;
        break;
      }
      $mail->readHeader($line);
      // echo $line;
      break;
    case 3:
      // body
      if(endsWith(trim($line), SIMPLIFIEDBOUNDARYCHECK)) {
        $state = 0;
        $mail->send();
        break;
      }
      $mail->appendBody($line);
  }
}

fclose($handle);

class Mail {

  private $to;
  private $subject;
  private $body = '';
  private $extraHeader = '';

  public function readHeader($line) {
    if(startsWith($line, 'To: ')) {
      $this->to = substr(trim($line), 4);
    }else if(startsWith($line, 'Date: ') || startsWith($line, 'From: ') || startsWith($line, 'Content-Type: ') || startsWith($line, ' boundary="') || startsWith($line, 'MIME-Version:')) {
      $this->extraHeader .= $line;
    }else if(startsWith($line, 'Subject: ')) {
      $this->subject = substr(trim($line), 9);
    }
  }

  public function appendBody($line) {
    $this->body .= $line;
  }

  public function send() {

    global $excludeReceiver;

    if(in_array($this->to, $excludeReceiver)) {
      echo "Suppressed To: $this->to, Subject: " . $this->subject . "\n";
      return;
    }

    $receiver = TEST ? TESTRECEIVER : $this->to;
    echo "To: $receiver, Subject: " . $this->subject . "\n";
    mail($receiver, $this->subject, $this->body, $this->extraHeader);
  }
}