wp_mail very slow

I have a question about the wp_mail() function. Right now I’m having the following button to send e-mail with:

echo '<input type="submit" name="send_button" value="verzenden" id="'.$post_id.'" class="send_post">';

which triggers a jQuery request:

Read More
jQuery(document).ready(function() {

jQuery(".send_post").click(function() {

    var post_id = $(this).attr('id');

    jQuery.ajax({
        async: false,
        type: 'get',
        url: nieuwsbrief.sendmail_url,
        data: 'postid=' + post_id,
            success: function(data) {
                alert(data);
                return false;
            }
    });
});

});

jQuery sends the request to sendmail.php. Which includes the following:

<?php
 include_once $_SERVER['DOCUMENT_ROOT'] . '/www/wordpress/wp-config.php';
 include_once $_SERVER['DOCUMENT_ROOT'] . '/www/wordpress/wp-load.php';
 include_once $_SERVER['DOCUMENT_ROOT'] . '/www/wordpress/wp-includes/wp-db.php';

 global $wpdb;

$subscribers = $wpdb->get_results( 
$wpdb->prepare(

"SELECT * FROM 
            wp_nwsbrf_subscribers 
                INNER JOIN 
                    wp_nwsbrf_couple
                        INNER JOIN 
                            wp_nwsbrf_categories
                                INNER JOIN
                                    wp_posts
                                    WHERE 
                                        wp_nwsbrf_subscribers.category_id = wp_nwsbrf_categories.category_id
                                        AND 
                                        wp_nwsbrf_couple.category_id = wp_nwsbrf_categories.category_id
                                        AND 
                                        wp_nwsbrf_subscribers.category_id = wp_nwsbrf_couple.category_id
                                        AND
                                        wp_posts.ID = wp_nwsbrf_couple.ID
                                        AND
                                        wp_nwsbrf_couple.ID = %d", $_GET['postid']) );


foreach ($subscribers as $subscriber) {

    $to = $subscriber->subscriber_mail.",";
    $title = $subscriber->post_title;
    $message = $subscriber->post_content;

    wp_mail($to, $title, $message);
}

?>

As you can see, the mail has to be sent in sendmail.php (url: nieuwsbrief.sendmail_url). But when I click on the send_button it takes minutes (3 or 4) to send the e-mail!? What am I doing wrong here?

Thanks for helping me out!

Related posts

2 comments

  1. What am i doing wrong here?

    Many, many things, not all performance related. Lets begin with the critical parts, then conclude on your performance issues and what can be done to mitigate and help

    AJAX API

    Firstly, you’re not using the AJAX API, and reinventing the wheel. It’s quite simple:

    JS:

    $.post( do_mail_sending.ajaxurl, {
        action: 'do_mail_sending',
        post_id: post_id
    
    }, function(response) {
        alert( response );
    });
    

    PHP:

    wp_localize_script( 'do_mail_sending', 'do_mail_sending', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );
    add_action( 'wp_ajax_do_mail_sending',  'do_mail_sending' );
    add_action( 'wp_ajax_nopriv_do_mail_sending',  'do_mail_sending' ); // and for logged out users
    function do_mail_sending() {
        $post_id = $_POST['post_id'];
        die( 'response data' );
    }
    

    Closing PHP Tag and Trailing Space

    PHP can be used as a scripting language, but you’ve veered into app development, so I’d recommend not having a closing PHP tag at the end of the document. Your code snippet above does this and it has a new line break space afterwards, which can cause issues.

    Security

    It would appear that although you’re running a prepare statement, not once are you checking if the user is actually allowed to do this.

    E.g. I can flood all your subscribers, and bring down your server with the following javascript snippet:

    while ( true ) {
        jQuery.ajax({
            async: false,
            type: 'get',
            url: nieuwsbrief.sendmail_url,
            data: 'postid=' + post_id,
                success: function(data) {
                    return false;
                }
        });
    }
    

    You don’t check that:

    • the user is logged in
    • the user has the necessary roles and capabilities
    • that the request came from your site
    • that the request came from the page with the email send form
    • make use nonces
    • check that the post itself exists

    Performance

    Your performance woes are a result of what you’re doing. Namely:

    • An expensive SQL query including multiple joins
    • Sending an email

    Both are inherently expensive, and the last one you’re doing a lot of. Emails themselves are costly to send server-wise, as it has to contact the SMTP server, and negotiate, and any remote requests are going to take time.

    There is little you can do to avoid this expense, as the core parts that make this up are inherently expensive.

    But that’s okay. You aren’t the first person to have to do something expensive, so your goal should be mitigation and redesign. You cant reduce the cost of the process, but you can modify the process so that it appears fast for the UI, and still gets done. Banks solved this in the early days by doing their hard number crunching overnight while the branch was closed in batch runs.

    Here are some ways of mitigating it:

    • Split the process up into multiple steps, instead of 1 AJAX request, do several, 1 to count how many emails need to be sent, then several afterwards, using a paging style technique to send them in batches of 10 or 20 depending on your servers response times.
    • Dont send emails at all, instead calculate what work needs to be done, then save it in the database as a task that needs doing. Then have a cron job run at a regular interval that sends the emails in the background.
    • Add notes to a queue of emails to be sent, and send them one by one, removing the item from the queue. Your newsletter may not be sent all at once, but it will be sent, and it won’t bring your server down while it happens. It also enables you to create a status page showing the mailing progress in realtime.

    Data Note

    That you need an SQL statement like that makes me wonder if your categories table and query cant be replaced with a custom taxonomy and a call to WP_Query. This way you could take advantage of the built in object and query caching

  2. In my case wp_mail() took over 90 seconds per email on form submits, bringing my site to a crawl. Totally forgot to correct the /etc/hosts config for my site, per the instructions below, now sendmail is < 3 seconds.

    https://www.digitalocean.com/community/questions/sendmail-is-slow-to-send-mail

    If you check your /var/log/mail.log you will probably find something like this: My unqualified host name (localhost) unknown; sleeping for retry. This is because sendmail requires “hostname” to be a fully qualified domain name. update your “/etc/hosts” file: nano /etc/hosts to look like this: 127.0.0.1 localhost.localdomain localhost yourhostname boom, fast sendmail.

Comments are closed.