Send Mail from raw body for testing purposes
Asked Answered
A

6

7

I am developing a PHP application that needs to retrieve arbitrary emails from an email server. Then, the message is completely parsed and stored in a database.

Of course, I have to do a lot of tests as this task is not really trivial with all that different mail formats under the sun. Therefore I started to "collect" emails from certain clients and with different contents.

I would like to have a script so that I can send out those emails automatically to my application to test the mail handling.

Therefore, I need a way to send the raw emails - so that the structure is exactly the same as they would come from the respective client. I have the emails stored as .eml files.

Does somebody know how to send emails by supplying the raw body?

Edit: To be more specific: I am searching for a way to send out multipart emails by using their source code. For example I would like to be able to use something like that (an email with plain and HTML part, HTML part has one inline attachment).

 --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--
Adiaphorism answered 18/2, 2011 at 17:0 Comment(4)
Did you find any solution to this problem?Wouldbe
No, unfortunately not.Adiaphorism
I think you are missing the main part, that with Subject Header, and with multipart section so your MTA will know what to do. Hence your .eml files are incomplete. In a multipart email, root part ( the one you are missing ) must contain something like this "Content-Type: multipart/alternative; boundary="_----------=_MCPart_465790590"". This will specify to any mimeparser wich part to read first, and so on.Disinclined
Some feedback would have been nice. Which of the answers have your tried, what went wrong, did you get errors or unexpected results? 3 people have tried to help you last week but nobody seems to get any credit or responds. If it doesnt work straight it away, we might be able to help.Conjoin
V
0

Using PHPMailer, you can set the body of a message directly:

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

If your mails contain any mime attachments, this will most likely not work properly, as some of the MIME stuff has to go into the mail's headers. You'd have to massage the .eml to extract those particular headers and add them to the PHPMailer mail as a customheader

Venule answered 18/2, 2011 at 17:6 Comment(1)
Unfortunately this doesn't help much. For example one interesting test case is that attachments in Apple Mail often have the disposition "inline" whereas most other clients only use "attachment". And therefore I would like to send something like: --Apple-Mail-4-698704782 Content-Disposition: inline; filename="bla.pdf" .... I know that certain header elements also need to go into the new mail, but that should be not that big of a issue: just filter the important stuff (received, sender, to, ...) and use the remaining header fields again.Adiaphorism
Y
0

You could just use the telnet program to send those emails:

$ telnet <host> <port>                  // execute telnet
HELO my.domain.com                      // enter HELO command
MAIL FROM: [email protected]           // enter MAIL FROM command
RCPT TO: [email protected]          // 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

If you really want to do this in PHP, you can use fsockopen() or stream_socket_client() family. Basically you do the same thing: talking to the mailserver directly.

// 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);
Youlandayoulton answered 14/5, 2014 at 21:17 Comment(2)
Yes, I need to do this in PHP. Can you elaborate on how this works with PHP? What are the potential pitfalls when talking to the mailserver directly? I can imagine there are some...Adiaphorism
This method is actually more reliable then using mail(). A downside is that you'll need to do some error handling yourself, but mail() fails in that, so maybe it's not a downside at all. And because you need it for testing, you don't need it perfect :)Youlandayoulton
C
0

You can just use the build in PHP function mail for it. The body part doesnt have to be just text, it can also contain mixed part data.

Keep in mind that this is a proof of concept. The sendEmlFile function could use some more checking, like "Does the file exists" and "Does it have a boundry set". As you mentioned it is for testing/development, I have not included it.

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

  //basic settings
  $from = "Example mail<[email protected]>";

  //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);
}
?>

Update:

After some more testing I found that all .eml files are different. There might be a standard, but I had tons of options when exporting to .eml. I had to use a seperate tool to create the file, because you cannot save to .eml by default using outlook.

You can download an example of the mail script. It contains two versions.

The simple version has two files, one is the index.php file that sends the test.eml file. This is just a file where i pasted in the example code you posted in your question.

The advanced version sends an email using an actual .eml file I created. it will get the required headers from the file it self. Keep in mind that this also sets the To and From part of the mail, so change it to match your own/server settings.

The advanced code works like this:

<?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");
?>
Conjoin answered 15/5, 2014 at 9:41 Comment(6)
Are you sure that would work? As I see you include some MIME information in the headers parameter. But would do I do with complex structured, multi level MIMEs that have several body and attachment parts? I am just not sure how to split that up. Can you elaborate on that?Adiaphorism
Thats why you set the boundary in the header. Each part of the body (html/plain text/attachment/inline image/etc) is split with a boundry. See your own example from apple.Conjoin
And you dont have to split it up. EG: Your entire example (with ` --Apple-Mail-159-396126150` etc) would be the body of the email. If you send it as is, you would receive an email with attachment. The attachment would fail however because of the [snip] you includedConjoin
If anything is unclear or you need an example zip or anything, let me know.Conjoin
Yes please, I would really an example I can directly test out. Currently I cannot really wrap my head around this.Adiaphorism
I can provide it to you tomorrow. Going to bed now.Conjoin
M
0

You could start here

http://www.dreamincode.net/forums/topic/36108-send-emails-using-php-smtp-direct/

I have no idea how good that code is, but it would make a starting point.

What you are doing is connecting direct to port 25 on the remote machine, as you would with telnet, and issuing smtp commands. See eg http://www.yuki-onna.co.uk/email/smtp.html for what's going on (or see Jasper N. Brouwer's answer).

Macrophysics answered 21/5, 2014 at 11:16 Comment(0)
O
0

Just make a quick shell script which processes a directory and call it when you want e.g. using at crontab etc

for I in ls /mydir/ do cat I | awk .. | sendmail -options

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

You could also just talk to the mail server using the script to send the emls with a templated body..

Octavo answered 21/5, 2014 at 16:2 Comment(2)
The point is of the question how to batch process the emls - it is the email client side of things handling the actual sending based on eml files.Adiaphorism
The emls can be split with awk?Octavo
B
0

Edit: I have added the code to Github, for ease of use by other people. https://github.com/xrobau/smtphack

I realise I am somewhat necro-answering this question, but it wasn't answered and I needed to do this myself. Here's the code!

<?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;
    }
}

Using that, you can simply beat PHPMailer into submission with a few simple commands:

    $from = '[email protected]';
    $to = '[email protected]';
    $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);
Buller answered 14/4, 2022 at 4:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.