3

Sorry, probably just a newbie question but here goes, I have a script that loops over a bunch ( sometimes thousands ) of members sending out email using code like so:

    $transport = Swift_SmtpTransport::newInstance('...', 25)
                                ->setUsername('...')
                                ->setPassword('...');

                $mailer = Swift_Mailer::newInstance($transport);
                $mailer->send($message);

and it usually works well, but sometimes I get this error: Expected response code 354 but got code "250", with message "250 2.1.5 ... Recipient ok

I don't know smtp well so I can only guess that what is happening is the 354 code is expected at the beginning of the send mail process but the 250 code is recieved from the last email sent - so the mailer is getting getting overloaded by being used too fast, I guess I should be waiting for each email to get truly finished (ie wait for the 250 code) before sending the next. I'd hate to just stick a sleep command after each email so is there a better way to protect against this error? Or is the cause something else?

Thanks

lost baby
  • 3,178
  • 4
  • 32
  • 53
  • I added an occasional $mailer->disconnect() - sleep - reconnect, but that throws the error:PHP Fatal error: Call to undefined method Swift_Mailer::disconnect(), I guess disconnect() isn't part of the api in swift mailer 4.0.5. Is there another method? – lost baby Jun 08 '12 at 17:18

1 Answers1

1

In summary:

  1. Main change to do: reuse your mail connection, and preferrably creation of message, so it is done only as needed, usually just once
  2. Check how you instantiate SwiftMailer and confirm it's the most optimal way
  3. Your SwiftMailer class version seems a bit old (4.0.5), you might check out more recent version

In more detail:

Reusage of instantiated objects. You are recreating your mail transport every time, causing overhead. You shouldn't be doing that. You can use batchSend() for big bunches of emails if it is supported. See example of usage in this question. An applied example:

$message = Swift_Message::newInstance(...)
  ->setSubject('A subject')
  ->setFrom(array('myfrom@domain.com' => 'From Me'))
  ->setBody('Your message')
;

while(list($targetadd, $targetname) = each($targetlist))
{
   $message->addTo($targetadd, $targetname);
}

$message->batchSend();

Note though that batchSend() has been removed in 4.1.0 RC1 of SwiftMailer. As far as I know though, it internally called send() in a loop, so you should be able to have the same effects with calling send() multiple times but reusing at least your mail transport so you wouldn't reinstantiate it every time (and if applicable, also creation of message).

From the batch sending example at the official documentation, you can send emails in a batch by using send() with

// Create the Transport
$transport = Swift_SmtpTransport::newInstance('localhost', 25);

// Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);

// Create a message
$message = Swift_Message::newInstance('Wonderful Subject')
  ->setFrom(array('john@doe.com' => 'John Doe'))
  ->setBody('Here is the message itself')
  ;

// Send the message
$failedRecipients = array();
$numSent = 0;
$to = array('receiver@domain.org', 'other@domain.org' => 'A name');

foreach ($to as $address => $name)
{
  if (is_int($address)) {
    $message->setTo($name);
  } else {
    $message->setTo(array($address => $name));
  }

  $numSent += $mailer->send($message, $failedRecipients);
}

Transport protocols. Another thing to note is that SwiftMailer is a wrapper class. What it actually uses under the hood is what you define. In your case you are using SMTP transport, which is better than Mail transport (mail() function), but possibly not the most optimal one.

You don't say if you explicitly want to use that and which environment you have, but in linux environment, you could invoke sendmail directly with something like

$transport = Swift_SendmailTransport::newInstance('/usr/sbin/sendmail -bs');

Which could give a better performance. There is more information on different transports in SwiftMailer usage documentation.

Class version. Based on your comment, you are using 4.0.5 version. Current version is 4.1.8., and there are things that have been changed since with batch sending, so you might want to check that out too.

EDIT: updated info about batchSend(), current version and manual links.

Community
  • 1
  • 1
eis
  • 51,991
  • 13
  • 150
  • 199
  • thanks for the reply, I use smtp as it is the standard way our other scripts use it and it seems recommended by the swift mailer docs. I only recreate the transport and mailer objects occasionally and only started to in an attempt to solve this problem but i know it looks like i do it every time in my code snippet. So my code is basically what you suggest except i do recreate much of the message every time as it varies by recipient. I will look into upgrading to the latest version but i'll need to ask the sys admin to do that. – lost baby Jun 19 '12 at 19:12
  • well, you could at least try to see if it makes a difference to use a different transport - that would help in narrowing down the problem. – eis Jun 23 '12 at 11:11
  • I also rereate the transport and mailer objects when the error occurs and run a sleep(30) call after each error to give the smtp server time to catch up. So far that seems to work well enough for now. Will try the different transport method when I get another chance to get back to this issue. Thanks eis. – lost baby Jun 25 '12 at 12:35