I had a ticket registered by the client today, informing me that the PHP mail()
function was selected on it on one of our Windows Server Server windows when trying to send attachments.
After the investigation, I was able to reproduce his problem. Messages containing small attachments of about 30-60 Kb in size took 15-20 seconds to be processed by the mail()
function. Large attachments around 360-500Kb took longer than the maximum script time allowed (90 seconds).
I was able to reproduce the problem on two different Windows 2003 servers and on a Windows 2008R2 server. I also tried three different versions of PHP (5.2.14, 5.2.17, and 5.3.6) - all 32-bit and all non-stream tables according to Microsoft's recommendations for running PHP on Windows).
In all cases, mail is sent via SMTP (i.e., it does not use the sendmail implementation). I tried three different SMTP scripts:
- Delivery directly to our smarthost SMTP cluster (running Exim)
- Delivery through the local IIS SMTP service, which transfers our smarthosts
- Delivery through the local IIS SMTP service, but with MX search and direct delivery
Regardless of the above, sending attachments is still suboptimal, which means that the problem cannot be fixed on a slow relay.
Then I ran the same code on our CentOS servers that did not have any of these problems, the mail()
function returned almost immediately. However, PHP on these servers is configured to use sendmail
.
Then I decided to write the PHP source code to find out what the implementation of the mail()
function looked like and found this code in ext/standard/mail.c
:
if (!sendmail_path) { #if (defined PHP_WIN32 || defined NETWARE) if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, headers, subject, to, message, NULL, NULL, NULL TSRMLS_CC) == FAILURE) { if (tsm_errmsg) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tsm_errmsg); efree(tsm_errmsg); } else { php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", GetSMErrorText(tsm_err)); } return 0; } return 1; #else return 0; #endif
TSendMail()
is implemented in another source file ( win32/sendmail.c
). Ultimately, all data sent to the SMTP server is transmitted synchronously using the Post()
function in sendmail.c
, which looks like this:
static int Post(LPCSTR msg) { int len = strlen(msg); int slen; int index = 0; while (len > 0) { if ((slen = send(sc, msg + index, len, 0)) < 1) return (FAILED_TO_SEND); len -= slen; index += slen; } return (SUCCESS); }
The send()
function is a winsock2
function.
I am wondering if the buffer size (8K by default corresponds to the KB article below) or the lack of tuning has some effect on large amounts of data. There are no calls to setsockopt()
to indicate the size of the buffer or any other parameters to optimize calls to send()
.
Perhaps the mail()
function in Windows using SMTP delivery is not designed to send large emails?
I would be interested to know if anyone else looked at this code or experienced the same thing.
Design Issues - Sending Small Data Segments over TCP with Winsock
To be clear, we already have an alternative solution for the client (SwiftMailer), so this is not about getting recommendations on alternatives.