使用原始邮件正文发送邮件以进行测试

6
我正在开发一个PHP应用程序,需要从邮件服务器检索任意电子邮件。然后,将完全解析消息并存储在数据库中。
当然,由于有各种各样的邮件格式,这项任务并不是非常简单的。因此,我开始从某些客户端和具有不同内容的邮件中“收集”邮件。
我想要一个脚本,这样我就可以自动向我的应用程序发送这些邮件,以测试邮件处理。
因此,我需要一种发送原始邮件的方法-以便结构与来自相应客户端的邮件完全相同。我已将电子邮件存储为.eml文件。
有人知道如何通过提供原始正文来发送电子邮件吗?
编辑: 更具体地说:我正在寻找一种使用源代码发送多部分电子邮件的方法。例如,我希望能够使用像这样的东西(一个包含纯文本和HTML部分的电子邮件,HTML部分有一个内联附件)。
 --Apple-Mail-159-396126150
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;

The plain text email!

--=20    

=20
=20

--Apple-Mail-159-396126150
Content-Type: multipart/related;
    type="text/html";
    boundary=Apple-Mail-160-396126150


--Apple-Mail-160-396126150
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html;
    charset=iso-8859-1

<html><head>
    <title>Daisies</title>=20
</head><body style=3D"background-attachment: initial; background-origin: =
initial; background-image: =
url(cid:4BFF075A-09D1-4118-9AE5-2DA8295BDF33/bg_pattern.jpg); =
background-position: 50% 0px; ">

[ - snip - the html email content ]


</body></html>=

--Apple-Mail-160-396126150
Content-Transfer-Encoding: base64
Content-Disposition: inline;
    filename=bg_pattern.jpg
Content-Type: image/jpg;
    x-apple-mail-type=stationery;
    name="bg_pattern.jpg"
Content-Id: <4BFF075A-09D1-4118-9AE5-2DA8295BDF33/tbg.jpg>

/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAASAAA/+IFOElDQ19QUk9GSUxFAAEB
[ - snip - the image content ]
nU4IGsoTr47IczxmCMvPypi6XZOWKYz/AB42mcaD/9k=

--Apple-Mail-159-396126150--

你找到这个问题的解决方案了吗? - user89862
不好意思,不行。 - hbit
我认为你错过了主要部分,即包含主题头和多部分部分,以便你的MTA知道该怎么做。因此,你的.eml文件是不完整的。在多部分电子邮件中,根部分(你缺少的那个)必须包含类似于“Content-Type: multipart/alternative; boundary="_----------=_MCPart_465790590""的内容。这将指定任何mimeparser首先读取哪个部分,以此类推。 - Adrian Toma
1
一些反馈会很好。你尝试了哪些答案,出了什么问题,是否遇到了错误或意外的结果?上周有3个人试图帮助你,但似乎没有人得到任何认可或回应。如果它不能立即工作,我们可能能够提供帮助。 - Hugo Delsing
6个回答

0

编辑:我已将代码添加到Github,以便其他人更轻松地使用。https://github.com/xrobau/smtphack

我意识到我有点晚回答这个问题,但它没有得到解答,而我自己需要做这件事。以下是代码!

<?php
    
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;

class SMTPHack
{
    private $phpmailer;
    private $smtp;

    private $from;
    private $to;

    /**
     * @param string $from
     * @param string $to
     * @param string $smtphost
     * @return void
     */
    public function __construct(string $from, string $to, string $smtphost = 'mailrx')
    {
        $mail = new PHPMailer(true);
        $mail->isSMTP();
        $mail->SMTPDebug = SMTP::DEBUG_SERVER;
        $mail->SMTPAutoTLS = false;
        $mail->Host = $smtphost;
        $this->phpmailer = $mail;
        $this->from = $from;
        $this->to = $to;
    }

    /**
     * @param string $helo
     * @return SMTP
     */
    public function getSmtp(string $helo = ''): SMTP
    {
        if (!$this->smtp) {
            if ($helo) {
                $this->phpmailer->Helo = $helo;
            }
            $this->phpmailer->smtpConnect();
            $this->smtp = $this->phpmailer->getSMTPInstance();
            $this->smtp->mail($this->from);
            $this->smtp->recipient($this->to);
        }
        return $this->smtp;
    }

    /**
     * @param string $data
     * @param string $helo
     * @param boolean $quiet
     * @return void
     * @throws \PHPMailer\PHPMailer\Exception
     */
    public function data(string $data, string $helo = '', bool $quiet = true)
    {
        $smtp = $this->getSmtp($helo);
        $prev = $smtp->do_debug;
        if ($quiet) {
            $smtp->do_debug = 0;
        }
        $smtp->data($data);
        $smtp->do_debug = $prev;
    }
}

使用这个,您可以通过几个简单的命令轻松地打败PHPMailer:

    $from = 'xrobau@example.com';
    $to = 'fred@example.com';
    $hack = new SMTPHack($from, $to);
    $smtp = $hack->getSmtp('helo.hostname');
    $errors = $smtp->getError();
    // Assuming this is running in a phpunit test...
    $this->assertEmpty($errors['error']);
    $testemail = file_get_contents(__DIR__ . '/TestEmail.eml');
    $hack->data($testemail);

0
你可以使用telnet程序发送这些电子邮件:
$ telnet <host> <port>                  // execute telnet
HELO my.domain.com                      // enter HELO command
MAIL FROM: sender@address.com           // enter MAIL FROM command
RCPT TO: recipient@address.com          // enter RCPT TO command
<past here, without adding a newline>   // enter the raw content of the message
[ctrl]+d                                // hit [ctrl] and d simultaneously to end the message

如果你真的想在PHP中实现这个功能,你可以使用fsockopen()stream_socket_client()函数族。基本上你需要做的就是直接与邮件服务器通信。
// open connection
$stream = @stream_socket_client($host . ':' . $port);

// write HELO command
fwrite($stream, "HELO my.domain.com\r\n");

// read response
$data = '';
while (!feof($stream)) {
    $data += fgets($stream, 1024);
}

// repeat for other steps
// ...

// close connection
fclose($stream);

是的,我需要用PHP来完成这个任务。您能详细说明一下如何使用PHP吗?直接与邮件服务器通信时可能会遇到哪些潜在问题?我可以想象出一些可能存在的问题... - hbit
这种方法比使用mail()更可靠。缺点是你需要自己处理一些错误,但mail()在这方面失败了,所以也许这并不是一个缺点。而且因为你只需要用它进行测试,所以并不需要完美 :) - Jasper N. Brouwer

0

你可以使用内置的PHP函数mail来实现。 body部分不仅可以是纯文本,还可以包含混合部分数据。

请记住,这只是一个概念验证。 sendEmlFile函数可能需要进行更多的检查,例如“文件是否存在”和“是否设置了边界”。正如你所提到的,它是用于测试/开发的,我没有包含它。

<?php
function sendmail($body,$subject,$to, $boundry='') {
  define ("CRLF", "\r\n");

  //basic settings
  $from = "Example mail<info@example.com>";

  //define headers
  $sHeaders  = "From: ".$from.CRLF;
  $sHeaders .= "X-Mailer: PHP/".phpversion().CRLF;
  $sHeaders .= "MIME-Version: 1.0".CRLF;


  //if you supply a boundry, it will be send with your own data
  //else it will be send as regular html email
    if (strlen($boundry)>0)
        $sHeaders .= "Content-Type: multipart/mixed; boundary=\"".$boundry."\"".CRLF;
    else
    {
      $sHeaders .= "Content-type: text/html;".CRLF."\tcharset=\"iso-8859-1\"".CRLF;
      $sHeaders .= "Content-Transfer-Encoding: 7bit".CRLF."Content-Disposition: inline";
    }

  mail($to,$subject,$body,$sHeaders);
}

function sendEmlFile($subject, $to, $filename) {
  $body = file_get_contents($filename);
  //get first line "--Apple-Mail-159-396126150"
  $boundry = $str = strtok($body, "\n");

  sendmail($body,$subject,$to, $boundry);
}
?>

更新:

经过更多测试,我发现所有的.eml文件都是不同的。可能有一个标准,但是在导出到.eml时我有很多选项。我必须使用一个单独的工具来创建文件,因为你不能使用Outlook默认保存为.eml

您可以下载一个邮件脚本示例。它包含两个版本。

简单版本有两个文件,一个是发送test.eml文件的index.php文件。这只是一个文件,我将你在问题中发布的示例代码粘贴在里面。

高级版本使用我创建的实际.eml文件发送电子邮件。它将从文件本身获取所需的标题。请记住,这也设置了邮件的ToFrom部分,因此请将其更改为与您自己/服务器设置相匹配。

高级代码的工作原理如下:

<?php
function sendEmlFile($filename) {
    //define a clear line
    define ("CRLF", "\r\n");

    //eml content to array.
    $file = file($filename);

    //var to store the headers
    $headers = "";
    $to = "";
    $subject = "";

    //loop trough each line
    //the first part are the headers, until you reach a white line
    while(true) {
        //get the first line and remove it from the file
        $line = array_shift($file);
        if (strlen(trim($line))==0) {
            //headers are complete
            break;
        }

        //is it the To header
        if (substr(strtolower($line), 0,3)=="to:") {
            $to = trim(substr($line, 3));
            continue;
        }

        //Is it the subject header
        if (substr(strtolower($line), 0,8)=="subject:") {
            $subject = trim(substr($line, 8));
            continue;
        }

        $headers .= $line . CRLF;
    }

    //implode the remaining content into the body and trim it, incase the headers where seperated with multiple white lines
    $body = trim(implode('', $file));

    //echo content for debugging
    /*
    echo $headers;
    echo '<hr>';
    echo $to;
    echo '<hr>';
    echo $subject;
    echo '<hr>';
    echo $body;
    */

  //send the email
  mail($to,$subject,$body,$headers);
}


//initiate a test with the test file
sendEmlFile("Test.eml");
?>

你确定那样会行吗?因为我看到你在headers参数中包含了一些MIME信息。但是对于具有多个主体和附件部分的复杂结构化、多级别MIME,我该怎么办呢?我不确定如何拆分它们。你能详细说明一下吗? - hbit
这就是为什么你在头部设置“boundary”的原因。正文的每个部分(html/plain text/attachment/inline image等)都会用一个边界进行分割。可以参考苹果公司的示例。 - Hugo Delsing
你不需要将它分开。例如:你的整个示例(包括 --Apple-Mail-159-396126150 等)将成为电子邮件的正文。如果您按原样发送它,您将收到一封带有附件的电子邮件。但是由于您包含了 [snip],所以附件会失败。 - Hugo Delsing
如果有任何不清楚的地方或需要示例zip文件等,请告诉我。 - Hugo Delsing
是的,请给我一个可以直接测试的例子。目前我还无法真正理解这个。 - hbit
我明天可以给你提供它。现在要睡觉了。 - Hugo Delsing

0

使用PHPMailer,您可以直接设置邮件正文:

$mail->Body = 'the contents of one of your .eml files here'

如果您的邮件包含任何MIME附件,则这很可能无法正常工作,因为一些MIME内容必须放入邮件头中。您需要对.eml进行处理,以提取这些特定的标头,并将它们作为自定义标头添加到PHPMailer邮件中。

不幸的是,这并没有太大帮助。例如,一个有趣的测试案例是,Apple Mail中的附件通常具有“inline”属性,而大多数其他客户端只使用“attachment”。因此,我想发送类似以下的内容: --Apple-Mail-4-698704782 Content-Disposition: inline; filename="bla.pdf" .... 我知道某些标题元素也需要放入新邮件中,但这应该不是什么大问题:只需过滤重要内容(接收、发件人、收件人等),然后再次使用剩余的标题字段即可。 - hbit

0

0

只需编写一个快速的 shell 脚本,处理一个目录并在需要时调用它,例如使用 atcrontab 等命令。

对于 ls /mydir/ 中的 I,执行以下操作:cat I | awk .. | sendmail -options

http://www.manpagez.com/man/1/awk/

你也可以使用脚本直接与邮件服务器通信,发送带有模板主体的emls


问题的重点在于如何批量处理eml文件——这是由电子邮件客户端处理实际发送的。 - hbit
awk可以用来分割eml文件吗? - Jay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接