Skip to main content

List an email job’s failed recipients, and retry one safely

Free Advanced

An add-on that queues a bulk send of its own email type can show the podcaster who didn’t get it, and let them retry one person, without reading Benecaster’s queue table. The example adds two REST routes for an add-on’s my_addon_invite job: one lists the failed recipients a page at a time, and one retries a single recipient. It uses three EmailQueue methods: get_job_recipients(), count_job_recipients() and retry_single().

A job is two things: the id you queued it with and the exact email type. The same id under another type is someone else’s job, so always pass both.

retry_single() doesn’t check which job a row belongs to. That is why the retry route first proves the row is one of this job’s failed rows. Never pass it an id straight from a request.

To retry everything at once, $queue->retry_failed( $job, 'my_addon_invite' ) needs no ownership check, because the job itself scopes it.

email and display_name come from the recipient’s WordPress account and are empty for a deleted user, so give your screen a fallback. Both routes check manage_options themselves; EmailQueue checks no capabilities.

Code

<?php
add_action( 'benecaster_boot', function ( \Benecaster\Container $container ): void {
    $queue = $container->make( \Benecaster\Email\EmailQueue::class );

    add_action( 'rest_api_init', function () use ( $queue ): void {
        register_rest_route( 'my-addon/v1', '/jobs/(?P<id>\d+)/failed', [
            'methods'             => 'GET',
            'permission_callback' => fn () => current_user_can( 'manage_options' ),
            'callback'            => function ( \WP_REST_Request $request ) use ( $queue ) {
                $job  = absint( $request['id'] );
                $page = max( 1, absint( $request['page'] ?? 1 ) );
                return [
                    'items' => $queue->get_job_recipients( $job, 'my_addon_invite', 'failed', $page, 50 ),
                    'total' => $queue->count_job_recipients( $job, 'my_addon_invite', 'failed' ),
                ];
            },
        ] );

        register_rest_route( 'my-addon/v1', '/jobs/(?P<id>\d+)/failed/(?P<qid>\d+)/retry', [
            'methods'             => 'POST',
            'permission_callback' => fn () => current_user_can( 'manage_options' ),
            'callback'            => function ( \WP_REST_Request $request ) use ( $queue ) {
                $job = absint( $request['id'] );
                $qid = absint( $request['qid'] );
                // retry_single() does not check the job, so prove the row is ours first.
                $ours = wp_list_pluck(
                    $queue->get_job_recipients(
                        $job, 'my_addon_invite', 'failed', 1,
                        max( 1, $queue->count_job_recipients( $job, 'my_addon_invite', 'failed' ) )
                    ),
                    'queue_id'
                );
                if ( ! in_array( $qid, $ours, true ) || ! $queue->retry_single( $qid ) ) {
                    return new \WP_Error( 'not_retryable', 'Not a failed row of this job.', [ 'status' => 400 ] );
                }
                return [ 'success' => true ];
            },
        ] );
    } );
} );

View on GitHub →

Need this built rather than just documented? See our services →