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}}.
Email Header and Footer Context
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 );
Customise Header/footer Branding
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.