I've built the following before and have referenced my own code to see if I missed anything and from what I can tell I have not.

I have a WP CRON scheduled and it executes as the scheduled intervals. When I run CRONS I always record their 'last run' time as an option in the DB so I can always jump in and check if it's running. So I know it's running. I've also installed WP Crontrol for the time being so I can manually trigger the CRON whenever I want.

So, I am 100% confident the WP CRON is working.

I also then have a function that queries a custom post-type looking for entries flagged that they need a 'reminder' sent. I then run an SQL query against a custom table that retrieves 'assignments' that match the CPT and tomorrow's date. For each of these, an email is sent.

The function works. If I call it anywhere else on the site, it executes. If I just drop it in an add_action() like add_action( 'wp_footer', 'my_reminder_emails', 100 ); it will properly execute and it will send me all of the appropriate email reminders. (For the time being I have all the emails coming to me rather than the assignees.)

Where it all breaks down is within the WP CRON. The WP CRON runs as scheduled, it updates it's last run time stamp and then... ...nothing.

HTTP Authentication is enabled. Instead of waiting for traffic on the development server I'm using WP Crontrol to fire the WP CRON. If I'm patient and just refresh the site every 5 minutes myself, I can see the WP CRON is running. But no emails are sent.

The site can send emails, that's not the issue. If I trigger the emailer function in ANY other way, it works. It only doesn't work when the WP CRON triggers it.

I have even taken the code from within my function and included it in the WP CRON (rather than having a separate function to call) and that also doesn't work.

Here's the WP CRON set-up:

function custom_cron_deactivate() { 
    $timestamp = wp_next_scheduled( 'custom_cron_hook' );
    wp_unschedule_event( $timestamp, 'custom_cron_hook' );
register_deactivation_hook( __FILE__, 'custom_cron_deactivate' );
function custom_cron_activation() {
    if( !wp_next_scheduled( 'custom_cron_hook' ) ) :  
        wp_schedule_event( time(), 'everyfiveminutes', 'custom_cron_hook' );  
add_action( 'wp', 'custom_cron_activation' );

function custom_cron_exec() {
    date_default_timezone_set( 'America/Los_Angeles' );
    $current_time       = date( 'F j, Y, g:i a' );
    update_option( 'customCRON_last', strtotime( $current_time ) );
    //run tasks
add_action( 'custom_cron_hook', 'custom_cron_exec' );

This is the executable function (I have removed some of the basic 'Query' stuff which I know works.

function custom_reminders_cron_task() {
    $today      = date( 'Y-m-d' );
    $tomorrow   = strtotime( date( 'Y-m-d', strtotime( $today . ' +1 day' ) ) );
    $rems_args  = array(
        'post_type'         => 'custom_position',
        'posts_per_page'    => -1,
        'post_status'       => 'publish',
        'fields'            => 'ids',
        'meta_query'        => array(
                'key'               => 'custom_send_reminder',
                'value'             => 1,
                'compare'           => '=',
                'type'              => 'NUMERIC',
    $rems   = get_posts( $rems_args );
    $remslist = implode( ',', $rems );
    global $wpdb;
    $custom_table       = $wpdb->prefix . 'custom_assignments';
    $rems_prepare   = $wpdb->prepare(
        "SELECT * FROM {$custom_table} WHERE `sdate ` = %d AND `posid` IN (%d)",
        $tomorrow, $remindlist
    $rems_results   = $wpdb->get_results( $rems_prepare );
    foreach( $rems_results as $send_rem ) :
        $subject    = 'Automated Assignment Reminder';
        $udata      = get_userdata( $send_rem->user_id );
        $uemail     = '[email protected]';
        $uname      = $udata->first_name . ' ' . $udata->last_name;
        $body       = '<h3>Custom Assignment Reminder</h3><p>' . $uname . ', <br/><br/>you have been assigned <strong>' . get_the_title( $send_rem->posid ) . '</strong> for ' . date( 'F j, Y', $send_rem->sdate ) . '.<br/><br/><small>This is an automatically generated reminder - please do not reply.</small></p>';
        $headers    = array(
            'Content-Type: text/html; charset=UTF-8',
            'From: Custom <[email protected]>'
        wp_mail( $uemail, $subject, $body, $headers );

As I said, every individual piece works. Just not in combination.

Any help would be greatly appreciated.

  • If you put custom_reminders_cron_task(); before the update_option( 'customCRON_last', strtotime( $current_time ) );, do you still see the option updated after a cron run?
    – kero
    Dec 7, 2022 at 17:01
  • I do. I tried changing the ordering in a bunch of different places but saw zero difference. :-( I even went so far as moving the update_option() into the function... ...it works when I run the function on it's own, doesn't work when I run it via the CRON. Dec 7, 2022 at 17:22
  • I'm genuinely getting to the point where I'm wondering if I should just do it using JS and AJAX. I'll record the time stamp, load the JS on every page and then use a time comparison condition to determine whether or not to run the code that sends the reminders. Just drop the CRON altogether. Dec 7, 2022 at 17:35
  • 1
    I actually think I solved this... ...and it's Time Zones. FFS! I'll post an answer once I figure out how to resolve it. Basically though, all of the dates, for simplicity, are stored in the DB using UTC/GMT. But, because I wanted to eventually run the CRON at 9:00am PST, in the CRON I set the Time Zone as PST or America/Los_Angeles. That means that every successive date function is adding 8 hours to the UTC. So when I query the assignments DB, I don't get any matching assignments, because my SQL query epoch date has an additional 8 hours added to it. Ugh! Dec 7, 2022 at 21:33
  • 1
    Glad to hear you found a possible solution. Feel free to answer your own question with this information, so that future visitors with a similar problem can find the solution faster
    – kero
    Dec 8, 2022 at 10:37

1 Answer 1


Turns out that the issue within the code was neither the WP CRON nor was it the function that performs a WP_Query and SQL query of a custom DB table. As standalone functions, they both worked perfectly.

The problem actually ended up being that I specified a timezone in the WP CRON using date_default_timezone_set( 'America/Los_Angeles' );.

I had added that because, once testing was finished, rather than running the WP CRON every 5 minutes the plan was to run it once daily at 9:00am PST because that's where the client is physically located and where their staff all work.

I tend to save dates/times in the DB as Universal Time Codes because I find it's a lot easier to run comparisons and not have to worry about how different utilities are formatting dates, etc. Anywhere I can, I convert dates using strtotime() before recording them.

So, all of the custom dates in the DB are stored using UTC in the GMT time zone. (Keeping it simple.)

However, in the CRON scheduler, when I specified PST as the time zone and THEN executed the second function to query the DB and send emails, what I was encountering was mis-matched 'dates' because the timezone shift generated different time codes, that were 8 hours behind.

$rems_prepare   = $wpdb->prepare(
    "SELECT * FROM {$custom_table} WHERE `sdate ` = %d AND `posid` IN (%d)",
    $tomorrow, $remindlist

So where I'm checking if sdate is equal to $tomorrow, the timezone change I initiated in the CRON meant that I never found any matching results. Super obvious.

So the solution was actually quite simple and there were two of them... ...I could either schedule the CRON using UTC/GMT to whatever the equivalent of 9:00am PST is (17:00:00 GMT) or I could just jump back into the UTC/GMT timezone at the start of the querying/emailing function like this:

function custom_reminders_cron_task() {
    date_default_timezone_set( 'UTC' );
    $today      = date( 'Y-m-d' );
    $tomorrow   = strtotime( date( 'Y-m-d', strtotime( $today . ' +1 day' ) ) );
    //run the rest of the code...

I have no idea if this will be of any use to anyone else because it's a pretty specific issue of my own making. I only managed to find it because I started the process of converting the whole thing to an AJAX function and then, once again switching the time zone, I realized that my SQL query wasn't returning any results and caught why pretty quickly when I was comparing the query parameters against what's in the DB.

I had it temporarily set for 9:00am EST this morning and when I checked my inbox this morning the emails were there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.