Skip to main content

UnsubscribeManager

\Benecaster\Email\UnsubscribeManager

Class Free

Manages subscriber email opt-out preferences and unsubscribe tokens, and enforces those preferences at dispatch time by hooking benecaster_email_should_send.

Preferences are stored per show as JSON in the benecaster_email_preferences user meta key, under a show_{show_id} key holding broadcast_opted_out, broadcast_opted_out_at, all_opted_out, all_opted_out_at and unsubscribe_token. The token is generated lazily and is stable once set.

There are two opt-out levels and the difference is the whole design. broadcast suppresses non-transactional email only – broadcasts and digests – while transactional types continue. all suppresses every Benecaster email, billing included; it is the level a subscriber has to choose deliberately.

Methods

Method Visibility Since Description
check_opt_out( bool $should_send, string $type, ?int $user_id, ?int $show_id ): bool Public benecaster_email_should_send callback - the single enforcement point. Returns false when the user has opted out at all, or when the type is non-transactional and they have opted out of broadcast. Passes $should_send through unchanged when either id is null, so site-level mail with no subscriber attached is never suppressed here.
is_opted_out( int $user_id, int $show_id, string $type = 'broadcast' ): bool Public Whether a user has opted out at the given level for a show. $type is broadcast or all. The supported way for an add-on to drop opt-outs from an audience before handing it to the email queue or an ESP.
get_preferences( int $user_id, int $show_id ): array Public The preference array for one user/show pair, or the default structure when nothing has been saved.
opt_out( ?int $user_id, int $show_id, string $type, string $method = 'api', string $token = '' ): void Public Records an opt-out. $method is how it was initiated and is recorded by UnsubscribeLog - one_click_header, footer_link, subscriber_dashboard, admin_action, api. $user_id may be null when opting out via token without being logged in: user meta is skipped in that case but the hook still fires.
resubscribe( ?int $user_id, int $show_id, string $type, string $method = 'api' ): void Public Clears an opt-out at the given level.
build_unsubscribe_url( int $user_id, int $show_id, string $type = 'broadcast' ): string Public The tokenised unsubscribe URL for a subscriber and show. Use this rather than composing the URL by hand when rendering an unsubscribe link from a custom template or landing page.
get_or_create_token( int $user_id, int $show_id ): string Public Returns the subscriber's unsubscribe token for a show, generating and persisting one on first call. Stable once set.
find_user_by_token( string $token, int $show_id ): ?int Public Resolves the user who owns an unsubscribe token, so the unsubscribe endpoint can identify a subscriber clicking a List-Unsubscribe link while logged out. Null when no user matches.
inject_unsubscribe_url( array $tags, string $type, ?int $user_id, ?int $show_id ): array Public Adds the unsubscribe URL to the merge-tag set for an outgoing email.
inject_list_unsubscribe_headers( array $headers, string $type, ?int $user_id, ?int $show_id ): array Public Adds List-Unsubscribe and one-click headers to an outgoing email.
register(): void Public Registers the hook callbacks. Called during boot.

Constants

Name Value Description
TRANSACTIONAL_TYPES welcome, token_reset, tier_change, migration_reminder, plan_bumped, threshold_warning, threshold_crossed, downgrade_available, subscription_receipt, renewal_reminder, payment_failed, cancellation_confirmation, subscription_cancelled The email types that bypass a broadcast opt-out. Private; read it through the benecaster_transactional_email_types filter, which is applied on every check. Thirteen entries, including the five billing-lifecycle types (subscription_receipt, renewal_reminder, payment_failed, cancellation_confirmation, subscription_cancelled).

Notes

A broadcast opt-out is a MARKETING preference. It means "stop sending me announcements", and it must never be read as consent to stop being told about the subscriber's own money.

The five billing-lifecycle types were absent from the protected set from the day it was written - this was never drift, and it was never true. git log -S subscription_receipt on the file returns nothing, while the documentation published the opposite the whole time. payment_failed is the one that costs the podcaster money directly: a subscriber who is never told their card failed does not fix it, and churns on a dunning cycle they could not see.

Four types look protected and are not. donation_thank_you, promote_grace_reminder, promote_grace_expired and manual_grant_expired are managed email types and appear in email settings, but they are not in TRANSACTIONAL_TYPES and a broadcast opt-out suppresses them.

Adding a type to the set does not make it unsuppressable. An all opt-out still stops everything, billing included, and that is the level a subscriber has to choose deliberately.

Add-ons protect their own transactional types via the benecaster_transactional_email_types filter, which is unchanged.