1

It is 1 AM and I am struggling for 3-4 hours to see what's wrong with my script...

My database has ~400 emails. I set $ChunkSize as counter for the loop and also to count which is the next chunk to be processed.

I've set some echo() to debug

echo "This is the " . $GLOBALS["ChunkSize"] . " chunk. <br>";

It should output what chunk is processed at that time. If I disable mail() then I don't get 503 Service Unavailable but every echo() displays at the same time, not in the order of processing.

I also found out that some emails arrive, but not to everyone. Also, if some emails are sent, that means foreach() should have processed at least one chunk, that means it should display at least one echo().

I've set break 1; so every time it breaks out of foreach() it should display the echo() with the chunk number processed by foreach() but it doesn't.

What I am doing wrong?

$connection = mysql_connect($hostname, $username, $password);
mysql_select_db($dbname, $connection);
$result = mysql_query("SHOW COLUMNS FROM `Emails`");
while($row = mysql_fetch_array($result)){
$Addresses[] = $row['Field'];}
$Subject = "Test";
$Message = "
Test
";
$Headers = array( EMPTY FOR SECURITY REASONS );
$Headers = implode( "\r\n" , $Headers );
$ChunkAddresses = 50;
$EmailChunkArray = array_chunk($Addresses, $ChunkAddresses);
$ArraySize = count ($EmailChunkArray);
$ChunkSize = 0;
ChunkLoop: {
    $EmailChunkArrayLoop = $GLOBALS["EmailChunkArray"];
    foreach ($EmailChunkArrayLoop[$GLOBALS["ChunkSize"]] as $ToChunkLoop) {
        if ($GLOBALS["ChunkSize"] <= $GLOBALS["ArraySize"]) {
            mail($ToChunkLoop,$GLOBALS["Subject"],$GLOBALS["Message"],$GLOBALS["Headers"]);
            echo "This is the " . $GLOBALS["ChunkSize"] . " chunk. <br>";
        } else if ($GLOBALS["ChunkSize"] == $GLOBALS["ArraySize"]){
            exit();}
        $GLOBALS["ChunkSize"]++;
        break 1;}
}
if ($GLOBALS["ChunkSize"] != $GLOBALS["ArraySize"]){
    echo "Test. <br>";
    goto ChunkLoop;
} else {
    echo "Finished! <br>";}
Sumutiu Marius
  • 421
  • 4
  • 18
  • Why oh why are you using globals? – Mikey Sep 14 '17 at 22:19
  • Because EmailChunkArray and other arrays are out of ChunkLoop. – Sumutiu Marius Sep 14 '17 at 22:20
  • Is this code run in a webpage ? – Calimero Sep 14 '17 at 22:20
  • @Calimero this is the entire webpage. It is a single php file that I put on FTP and access it in browser every time I want to send a newsletter to the emails in the database. – Sumutiu Marius Sep 14 '17 at 22:21
  • @Calimero well, indeed this is not the entire script. There is Message which is the HTML email content, Headers which are the headers for email(), Subject, the subject for the email, also the SQL part that gets all the emails from the database into $Addresses. – Sumutiu Marius Sep 14 '17 at 22:24
  • 2
    @SumutiuMarius I suspect you are reaching the `max_execution_time` in your script (usually 5 minutes, check your php settings), maybe consider running it in command-line instead – Calimero Sep 14 '17 at 22:35
  • @Calimero I don't have access to command line since it is a webhost, not a VPN. And my max_execution_time is -1 – Sumutiu Marius Sep 14 '17 at 22:38
  • @Don'tPanic That goto will send the execution back to foreach after break 1; to process the other chunks. – Sumutiu Marius Sep 14 '17 at 22:38
  • 1
    nobody sends batch of mail via web facing script. write cli script that will check for some operation queue and will do Your job. sending mail is not so fast operation. – num8er Sep 14 '17 at 22:43
  • @SumutiuMarius even then, keeping a webpage loading for too long is not safe, especially with a remote server over which you don't have control. As an alternative you could split the task into smaller batches (i.e. no more than 100 mails sent in one go). – Calimero Sep 14 '17 at 22:43
  • @num8er I can't because, as said, it is a webserver, not a VPS (I said VPN earlier by mistake and just noticed). I don't have access to terminal or SSH. – Sumutiu Marius Sep 14 '17 at 22:46
  • @Calimero that's why I used array_chunks(). As you can see, I split them in chunks of 50. – Sumutiu Marius Sep 14 '17 at 22:47
  • PHP uses output buffering, not every `echo` you execute immediately displays in the browser. See [ob_flush](http://www.php.net/ob_flush) for more info on this. – ccKep Sep 14 '17 at 22:47
  • @SumutiuMarius it doesn't help in that case. The webpage (and the script) has to finish loading at some point, and that point is early (typical webpage loads under a few secs at most). That's why I suggest splitting the task across several scripts to keep it as brief as possible. – Calimero Sep 14 '17 at 22:50
  • @Calimero That won't be convenient because the email database increases it's size every time someone subscribes to the newsletter, therefore, the number of chunks will increase over time. – Sumutiu Marius Sep 14 '17 at 22:56
  • 1
    @SumutiuMarius ok, You just have web hosting. So there are few solutions: 1. use `mailgrid` service - push all Your mailings to them and don't think about complex things, 2. use `mailchimp` service - it's `mail subscription system`, You push mail text and it sends Your mail to subscribers, 3. there are tons of services that give free dedicated server instances for Your needs: `DigitalOcean` (coupon code: `DROPLET10`), `Heroku`, `C9` and many more if You search the internet. If business wants to achieve money - business must waste money for tech. – num8er Sep 14 '17 at 22:59
  • You are right, my suggestion is just a quick hack to help you go to bed tonight. Real solutions, if I am right on this, would be to run your script through command-line (on your machine or another) or modify your script so that it never sends more than X emails per run, starting with a low limit (50) and going up slowly until it breaks. Or use any other paid service that is recommended to you for massive e-mailing. – Calimero Sep 14 '17 at 23:01
  • 1
    @SumutiuMarius keep in mind - developer's time is expensive resource than tech that could be bought to speed up progress. You wasted 1-2 days for that problem when You could waste 1 hr with right service/tools that would be bought by customer and quickly switch to do another profitable tasks. – num8er Sep 14 '17 at 23:06
  • 1
    @num8er I will be thinking about that when I will be opening a business. So far I only run an Youtube channel from which I barely get 30$/month from which I need to pay the web hosting and the radio station. Unless you're willing to pay me a VPS, I will stick to doing things by myself. I came here to get help with something I don't understand, not to be sent to paid services that I don't have money for. I know there are services like that and if I had money, I wouldn't have been here in the first place. – Sumutiu Marius Sep 14 '17 at 23:12
  • Let's not do off topic. Maybe there are people who have other ideas. – Sumutiu Marius Sep 14 '17 at 23:15
  • 1
    @SumutiuMarius minimal droplet in DigitalOcean is 5$, with coupon code `DROPLET10` You can get 10$ bonus and have 2 months of free. Also use mailchimp, mailgrid - are giving free batch mail sending, mail subscription services. Also I can tell You following: create php script that will only send an email and using process forking technique call that script in background asynchronously (https://www.electrictoolbox.com/article/php/process-forking/) or use `pthreads` (https://www.mullie.eu/parallel-processing-multi-tasking-php/) or also call it with nohup (https://php7.wordpress.com/fork-php/) – num8er Sep 14 '17 at 23:20

1 Answers1

2

Create script that will only do one thing - send mail.

sendMail.php

<?php

// Get recipient from the argv array
$recipient = $_SERVER['argv'][1];

// Mail args
$subject = 'HELLOOOOOOO';
$message = 'BLablabla';
$headers = [...]; // optional or not

// Send it
mail($recipient, $subject, $message, $headers);

And inside of Your code where You do:

mail($ToChunkLoop,$GLOBALS["Subject"],$GLOBALS["Message"],$GLOBALS["Headers"]);

Replace with:

$recipient = escapeshellarg($ToChunkLoop);
exec("php /path/to/sendMail.php ".$recipient." > /dev/null &"); // that will call mail script and will not wait when execution will end

Feel free to adapt my code examples as You wish

P.S. this solution is for cases when You don't want to pay for normal batch mail sending, mail subscription or dedicated, vps services and have just small web hosting. (:

P.S.. it's not a brilliant solution, but done for requirements provided by question author

num8er
  • 18,604
  • 3
  • 43
  • 57
  • @SumutiuMarius I'm glad that it's working. But keep in mind to be less noisy - don't send 50 at a time, better make 100 ms pause `sleep(100000)` between each call. Otherwise mail server may think that script is spamming and etc. – num8er Sep 15 '17 at 00:04
  • It doesn't send 50 at a time. That's the number of emails in every chunk but foreach executes SendMail.php for each email in the chunk, or at least that's what I understand. – Sumutiu Marius Sep 15 '17 at 00:09
  • And I think you meant `usleep()` because `sleep(100000)` would mean 27 hours :) – Sumutiu Marius Sep 15 '17 at 00:13
  • Can I send more arrays through `exec()`? For example: `exec("php /SendMail.php ".$Receiver. , .$AnotherArray." > /dev/null &")` – Sumutiu Marius Sep 15 '17 at 00:17
  • 1
    I did an edit. I'd like if you could take a look at what I modified. I am not an expert in PHP and not sure if what I did is good. I exported the entire chunk of emails through `exec()` and set the `foreach()` inside the `SendMail.php` file so `mail()` will be executed for every array in the chunk that was sent by `exec()`. – Sumutiu Marius Sep 15 '17 at 00:48