benecaster_bulk_enrollment_complete
Fires once, after the whole batch finishes processing — including on empty input — whether every row succeeded, some failed, or all failed.
It does not fire when BulkEnrollmentProcessor::enroll() refuses the batch before the first row by throwing — an unknown token type, a missing dependency, or a subscriber batch on a show whose active bridge is not the built-in membership (BulkEnrollBridgeNotBuiltin, including a show with no bridge at all). A queued job refused that way at the cron tick completes (status: complete) with every row an error (message: bridge_not_builtin) and still does not fire this action, because no row was processed. Listeners counting batches, or relying on one call per queued job, should not assume this action fires for every job that was started.
$skipped aggregates invalid-email rows and already-enrolled rows (plus, for a follower batch, rows refused by the unlicensed follower cap) into a single “no-op” bucket. For the breakdown, or for per-row detail, inspect the structured rows return value of BulkEnrollmentProcessor::enroll() rather than this action.
A batch can enroll subscribers on a tier, or free followers — check $token_type before treating $enrolled as new paying members. For a follower batch, $tier_slug is the show’s follower slug (not a gating tier), and $enrolled counts new followers including ones sent a double-opt-in confirmation instead of a token.
$token_type was appended as a sixth argument in a later release. Listeners registered with the original five arguments (accepted_args = 5) are unaffected; register with accepted_args = 6 to read it.
Import a follower list from code
Add a list of email addresses as free followers from an external system or your own code. Subscribers → Bulk enroll → Add as: Followers is the admin screen for this; these are the two programmatic routes to the same code. Use it for a mailing list, a Patreon export’s free members, or a newsletter you are folding into the show — anywhere you have addresses and want each one to get a follower feed.
From another system
Call the REST endpoint with an application password for an administrator. Leave out tier_slug — a follower is not a tier:
curl -X POST https://your-site.com/wp-json/benecaster/v1/shows/42/subscribers/bulk-enroll \
-u 'admin-user:xxxx xxxx xxxx xxxx xxxx xxxx' \
-H 'Content-Type: application/json' \
-d '{"token_type":"follower","addresses":["ana@example.com","ben@example.com"]}'
The batch runs on the next WP-Cron tick; poll GET /shows/42/subscribers/bulk-enroll/status for the report. Up to 1,000 addresses per request.
In-process
From an add-on or a WP-CLI command in your own plugin, run it synchronously and read the report directly — see the code example.
An address that already has a token for the show is left exactly as it is — reported already_enrolled, not converted, not emailed. That includes paying subscribers: an import of your whole mailing list will not turn your paying members into followers. Do not try to “fix” a row by revoking and re-importing it; a revoked token still counts as having one.
Every new follower is sent a welcome email, even when the show’s welcome email is switched off — it is the only way a follower receives their feed URL (the plaintext token exists once, at generation). Warn whoever owns the list before you run it.
The 100-follower cap on an unlicensed install applies to imports too, and has no filter. Rows past it come back skipped with message: follower_cap_reached; handle that rather than looking for a way around it. On a connected show the cap does not apply.
Double opt-in is honoured. If a site returns true from benecaster_follower_double_optin, imported addresses are sent a confirmation email instead of a token, and the row reads created with message: confirmation_sent. $enrolled on benecaster_bulk_enrollment_complete counts them too — check $token_type (the action’s sixth argument) before treating $enrolled as new paying members.
token_type accepts only subscriber and follower; anything else is refused (400 over REST, InvalidArgumentException in-process).
Not at a keyboard and need someone added one at a time instead? See Build a custom follower signup UI.
<?php
add_action( 'benecaster_boot', function ( \Benecaster\Container $container ): void {
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
return;
}
\WP_CLI::add_command( 'my-addon import-followers', function ( array $args, array $assoc ) use ( $container ): void {
$show_id = absint( $assoc['show'] ?? 0 );
$addresses = file( (string) ( $args[0] ?? '' ), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ) ?: [];
$report = $container
->make( \Benecaster\Membership\BulkEnrollmentProcessor::class )
->enroll( $addresses, $show_id, '', token_type: 'follower' );
foreach ( $report['rows'] as $row ) {
if ( 'follower_cap_reached' === ( $row['message'] ?? '' ) ) {
\WP_CLI::warning( "{$row['email']}: follower limit reached — connect the show to add more." );
}
}
\WP_CLI::success( sprintf( 'Added %d, already on the show %d, skipped %d, errors %d.',
$report['enrolled'], $report['already_enrolled'], $report['skipped'], $report['errors'] ) );
} );
} );
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
$show_id |
int |
— | ID of the show |
$tier_slug |
string |
— | Tier slug the batch enrolled into — for a follower batch, the show's follower slug |
$enrolled |
int |
— | Rows that produced a new subscription, or for followers a new follower (including confirmation-sent rows) |
$skipped |
int |
— | Invalid-email rows + already-enrolled rows (+ rows refused by the follower cap) |
$errors |
int |
— | Rows that threw during processing |
$token_type |
string |
— | `subscriber` or `follower`. Appended in a later release — omit it from your callback signature to keep working with the original five arguments. |
Examples
Alert admin when enrollments fail
add_action( 'benecaster_bulk_enrollment_complete', function (
int $show_id,
string $tier_slug,
int $enrolled,
int $skipped,
int $errors,
string $token_type
): void {
if ( $errors > 0 ) {
wp_mail(
get_option( 'admin_email' ),
'Bulk enrollment completed with errors',
"Show: {$show_id}. Type: {$token_type}. Enrolled: {$enrolled}. Skipped: {$skipped}. Errors: {$errors}."
);
}
}, 10, 6 );