Skip to main content

Customizing Email Templates

Benecaster’s default email templates are minimal by design — they work without configuration. When you want to match your brand, change the copy, or reroute sends to a third-party service, every part of the email pipeline is customisable via PHP filters.


Theme Overrides

Copy any template file into your theme (or child theme) to override it. Benecaster checks child theme first, then parent theme, then falls back to its own default.

Override path: {theme}/benecaster/emails/{template}.php

Template file What it controls
base.php The outer HTML shell — doctype, <html>, <body>, and the container <table> that wraps all content; the body slot is injected here. Override to change structural markup, outer background colour, or container width.
email-header.php Rendered above the email body inside base.php — logo, accent colour, show name
email-footer.php Rendered below the email body for subscriber-facing emails — unsubscribe link, show name
email-admin-footer.php Rendered below the email body for admin emails — no unsubscribe link
welcome.php Body of the welcome email
token-reset.php Body of the token reset email
tier-change.php Body of the tier change email
donation-thank-you.php Body of the donation thank-you email sent after a listener logs a donation reference
subscription-receipt.php (Phase 2) Body of the billing receipt email sent on each successful payment
renewal-reminder.php (Phase 2) Body of the renewal reminder email sent N days before the billing period ends
payment-failed.php (Phase 2) Body of the payment failure email sent once per Stripe retry cycle
subscription-cancelled.php (Phase 2) Body of the cancellation confirmation email
promote-grace-reminder.php (Phase 2) Grace period reminder sent 6–8 days before a promoted subscriber’s feed token expires
promote-grace-expired.php (Phase 2) Expiry notice sent when a promoted subscriber’s grace period ends and their token is revoked

Variables Available in Templates

Every template has access to all resolved merge tags. The specific variables available depend on the email type — see Email Merge Tags for the full list. Common global variables always set:

$subscriber_name   // Subscriber's display name
$subscriber_email  // Subscriber's email address
$show_title        // Show display name
$show_url          // Show's public URL
$site_name         // WordPress site name
$unsubscribe_url   // HMAC-signed unsubscribe URL

Type-specific variables in welcome.php:

$feed_url            // Subscriber's private RSS feed URL
$tier_name           // Subscriber's tier display name
$apple_podcasts_url  // Deep link for Apple Podcasts
$overcast_url        // Deep link for Overcast
$pocket_casts_url    // Deep link for Pocket Casts
$castro_url          // Deep link for Castro

Type-specific variables in token-reset.php:

$feed_url            // New feed URL after reset
$apple_podcasts_url  // Deep link for new URL
$overcast_url
$pocket_casts_url
$castro_url
$custom_message      // Admin's optional message; empty string if not provided

Type-specific variables in tier-change.php:

$old_tier_name  // Previous tier display name
$new_tier_name  // New tier display name

Type-specific variables in donation-thank-you.php:

$show_name         // Show display name
$donation_amount   // Formatted amount with ISO 4217 currency code, e.g. "USD 12.50";
                   // empty string when the donor did not provide an amount
$donation_platform // Human-friendly platform label, e.g. "Ko-fi", "PayPal", "Buy Me a Coffee";
                   // falls back to the raw platform slug when the label is not recognised
$site_name         // WordPress site name
$donor_name        // Donor's name; empty string when not collected — always guard against empty
$donor_message     // Message from the donor; empty string when not collected
$donation_date     // Date of the donation, formatted per WordPress date settings

The donation_thank_you email is only sent when the donor provides an email address during submission. It is skipped silently for anonymous donations. Always guard against empty $donor_name and $donor_message in templates — Stripe donations populate these when the donor supplies them; anonymous or link-mode donations do not.

Type-specific variables in subscription-receipt.php (Phase 2):

$amount_formatted    // Invoice amount, e.g. "$12.00"
$invoice_number      // Stripe invoice number, e.g. "INV-0001"
$invoice_hosted_url  // Link to the Stripe-hosted invoice page
$invoice_pdf_url     // Link to download the invoice PDF
$tier_name           // Subscriber's tier display name
$period_end_formatted // Formatted next renewal date

Type-specific variables in renewal-reminder.php (Phase 2):

$tier_name           // Subscriber's tier display name
$renewal_date        // Formatted renewal date
$days_until_renewal  // Integer: number of days until the billing period ends
$amount_formatted    // Renewal amount (reserved; empty string until Email Editor add-on ships)
$billing_interval    // 'month' or 'year'

Type-specific variables in payment-failed.php (Phase 2):

$tier_name           // Subscriber's tier display name

Type-specific variables in subscription-cancelled.php (Phase 2):

$tier_name           // Subscriber's tier display name
$period_end_formatted // Formatted date when access ends (if cancel_at_period_end was set)

All Phase 2 billing templates also receive the standard global variables ($subscriber_name, $show_title, $show_url, $site_name, $unsubscribe_url).

Type-specific variables in promote-grace-reminder.php and promote-grace-expired.php (Phase 2):

$tier_name             // The subscriber's original native tier name
$grace_period_end_date // Formatted expiry date
$target_plugin_name    // Name of the destination membership plugin

Available as merge tags: {{tier_name}}, {{grace_period_end_date}}, {{target_plugin_name}}.

The header and footer templates receive a $args array filtered via benecaster_email_wrapper_args. Default keys:

$args['logo_url']        // Header logo URL
$args['show_name']       // Show name in header/footer
$args['accent_color']    // Hex colour for header background
$args['footer_text']     // Footer text (includes unsubscribe link markup)
$args['is_admin_email']  // bool — controls which footer template loads

The Email Editor add-on takes over header and footer rendering when active — template overrides for email-header.php and email-admin-footer.php are bypassed.


Key Filters

Gate Whether an Email Sends

// Suppress all emails of one type:
add_filter( 'benecaster_email_should_send_welcome', function( bool $send, ?int $user_id, ?int $show_id ): bool {
    return false; // suppress welcome; send your own
}, 10, 3 );

Shared filter: benecaster_email_should_send( $should_send, $email_type, $user_id, $show_id ) — fires for every email type.

Important: Never suppress transactional emails (welcome, token_reset, token_revoked) for compliance reasons. Only suppress broadcast or marketing type emails.

Change the Feed URL in the Welcome Email

The feed URL placed in the welcome email passes through benecaster_token_url before embedding. Use this filter to switch to a pretty-permalink format:

add_filter( 'benecaster_token_url', function( string $url, string $token, int $show_id ): string {
    return home_url( '/listen/' . $token );
}, 10, 3 );

Customise Merge Tags per Type

// Add a custom merge tag to welcome emails only:
add_filter( 'benecaster_email_merge_tags_welcome', function( array $tags, ?int $user_id, ?int $show_id ): array {
    $tags['podcast_network_name'] = get_option( 'my_network_name', '' );
    return $tags;
}, 10, 3 );

Shared variant: benecaster_email_merge_tags( $tags, $email_type, $user_id, $show_id ) — fires for every email type. See Email Merge Tags for the full tag catalog.

Tier Change Email

// Suppress tier change email for specific scenarios:
add_filter( 'benecaster_email_should_send_tier_change', function( bool $send, ?int $user_id, ?int $show_id ): bool {
    // return false to suppress
    return $send;
}, 10, 3 );
// Add a custom tag to tier change emails:
add_filter( 'benecaster_email_merge_tags_tier_change', function( array $tags, ?int $user_id, ?int $show_id ): array {
    $tags['support_url'] = 'https://example.com/support';
    return $tags;
}, 10, 3 );
add_filter( 'benecaster_email_wrapper_args', function( array $args, string $email_type, int $show_id ): array {
    $args['logo_url']     = 'https://example.com/custom-email-logo.png';
    $args['accent_color'] = '#1a2e3f';
    return $args;
}, 10, 3 );

Does not fire when the Email Editor add-on is active.


Protecting Transactional Emails from Opt-out Suppression

By default, all email types are subject to opt-out suppression when a subscriber has opted out of broadcast emails — except for a protected set of transactional types. The default protected set:

welcome, token_reset, token_revoked, tier_change,
migration_reminder, donation_thank_you,
subscription_receipt, renewal_reminder, payment_failed, subscription_cancelled,
promote_grace_reminder, promote_grace_expired

The Phase 2 billing email types are all transactional — subscribers expect receipt, reminder, and cancellation emails regardless of their broadcast opt-out status.

If your add-on introduces a custom email type that should bypass broadcast opt-out suppression, register it via the benecaster_transactional_email_types filter:

add_filter( 'benecaster_transactional_email_types', function( array $types ): array {
    $types[] = 'my_addon_delivery_email';
    return $types;
} );

Types added here are still suppressed when the subscriber has opted out of all emails.


Unsubscribe Endpoint

Benecaster provides a built-in unsubscribe endpoint at:

?benecaster_unsub={token}&benecaster_unsub_show={id}&benecaster_unsub_type={broadcast|all}

A {{unsubscribe_url}} merge tag is automatically populated in all subscriber-facing emails. You can replace the default URL with a custom branded opt-out page via the benecaster_email_merge_tags filter — see add-custom-merge-tags-to-emails.

To gate unsubscribe processing (for fraud detection or rate limiting only — never to prevent legitimate opt-outs):

add_filter( 'benecaster_email_unsubscribe_allowed', function( bool $allowed, ?int $user_id, int $show_id, string $type, string $token ): bool {
    // return false only for fraud/abuse prevention
    return $allowed;
}, 10, 5 );

See Unsubscribe Handling for the subscriber-facing flow.


REST Endpoints

For programmatic configuration (integration tests, headless setups, or custom admin UIs):

Endpoint Purpose
GET /benecaster/v1/email-settings Read current email settings including emails_per_hour and queue depth
POST /benecaster/v1/email-settings Update email settings — accepts emails_per_hour (int, 10–2000)

Both require manage_options capability and a valid WP REST nonce.