Easy Digital Downloads ships with functional, workable email notifications out of the box. But “workable” and “on-brand” are two very different things. If you’ve ever wanted to swap EDD’s default purchase receipt for something that matches your store’s identity — custom colors, your logo, a personalized message, conditional content per product. This guide covers exactly how to do that at the PHP level.
We’ll walk through the templating system EDD uses, the core hooks and filters available, and how to build a fully custom email template without touching the plugin files directly. Everything here uses EDD’s extension API, which means your changes survive updates.
How EDD’s Email System Works
Before writing a line of code, it’s worth understanding EDD’s email architecture. This saves you from overriding the wrong hooks and helps you target exactly the output you want to change.
EDD handles transactional emails through the EDD_Emails class, located in includes/emails/class-edd-emails.php. This class manages the email header, body, and footer as separate template parts. The class wraps content in an HTML envelope and sends it via wp_mail().
The Three Template Parts
- Header: The opening HTML, doctype,
<head>block, and the email header image area. - Body: The dynamic content specific to each email type (purchase receipt, sale notification, etc.).
- Footer: The closing HTML and footer text configured in EDD settings.
Each of these parts is filterable. EDD also supports full template file overrides, following the same pattern as WooCommerce — you copy template files into your theme or a custom plugin, and EDD loads yours instead of its own.
Method 1: Template File Overrides
The cleanest way to customize EDD email templates is to override the template files. EDD checks your active theme and parent theme for template overrides before loading its own files.
Where EDD Looks for Templates
EDD loads email templates from two locations, in order:
yourtheme/edd_templates/emails/yourtheme/edd/emails/edd/includes/emails/email-template.php(plugin default)
This means you can create a folder called edd_templates/emails/ in your theme directory, drop in a customized copy of email-template.php, and EDD will automatically pick it up.
# Copy the default template into your theme
cp wp-content/plugins/easy-digital-downloads/includes/emails/email-template.php \
wp-content/themes/your-theme/edd_templates/emails/email-template.php
Open your copy and you’ll see it’s a standard PHP/HTML file. You can change the table structure, add inline styles, add a logo, or restructure the layout entirely.
Adding a Custom Logo
Inside the copied template file, locate the header section. The default output is driven by the edd_email_header action. You can replace it with your own output:
<?php
// In your theme's edd_templates/emails/email-header.php
$logo_url = get_stylesheet_directory_uri() . '/images/email-logo.png';
?>
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center">
<tr>
<td align="center" style="padding: 20px 0;">
<img src="<?php echo esc_url( $logo_url ); ?>"
alt="<?php echo esc_attr( get_bloginfo('name') ); ?>"
width="200" style="display:block;" />
</td>
</tr>
</table>
Always use absolute URLs for images in emails. Relative paths will break in email clients. Use get_stylesheet_directory_uri() for theme images, or store the logo URL in a theme option and pull it in.
Method 2: Filters and Actions for Fine-Grained Control
If you only need to tweak specific parts of the email output rather than replace the entire template, EDD provides a comprehensive set of hooks. This approach is better for small-to-medium customizations and keeps your code lean.
The Most Useful Email Filters
| Filter | What It Controls | Default Value |
|---|---|---|
edd_email_header_output | Full header HTML string | EDD header template |
edd_email_footer_output | Full footer HTML string | EDD footer template |
edd_email_message | The main body content string | Purchase receipt text |
edd_purchase_receipt_email_subject | Subject line for purchase receipts | “Purchase Receipt” |
edd_get_email_template_tags | Array of available template tags | Built-in EDD tags |
edd_email_from_name | Sender display name | Site name from settings |
edd_email_from_address | Sender email address | Admin email |
Customizing the Email Body
The edd_email_message filter runs on the body content before it’s wrapped in the email shell. Use it to append or prepend content:
add_filter( 'edd_email_message', 'my_custom_edd_email_message', 10, 2 );
function my_custom_edd_email_message( $message, $payment_data ) {
// Append a custom support note at the end of the receipt
$message .= '<p style="color:#555;font-size:14px;">';
$message .= 'Questions? Reply to this email or visit our <a href="' . esc_url( home_url('/support/') ) . '">Support Center</a>.';
$message .= '</p>';
return $message;
}
The second parameter $payment_data gives you access to the full payment object, so you can make the message conditional based on what was purchased, the total amount, customer information, or anything else stored with the payment.
Replacing the Header Entirely
add_filter( 'edd_email_header_output', 'my_custom_edd_header', 10, 1 );
function my_custom_edd_header( $output ) {
ob_start();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><?php echo esc_html( get_bloginfo('name') ); ?></title>
<style type="text/css">
body { margin: 0; padding: 0; background-color: #f7f7f7; }
table { border-collapse: collapse; }
.header { background-color: #1a1a2e; padding: 24px 40px; }
</style>
</head>
<body>
<table width="100%" cellpadding="0" cellspacing="0">
<tr><td>
<table class="header" align="center" width="600" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<img src="<?php echo esc_url( get_stylesheet_directory_uri() . '/images/logo.png' ); ?>"
alt="<?php echo esc_attr( get_bloginfo('name') ); ?>" />
</td>
</tr>
</table>
<?php
return ob_get_clean();
}
Method 3: Adding Custom Template Tags
EDD’s email system supports template tags — placeholders like {name}, {download_list}, and {sitename} that are replaced with dynamic values when the email sends. You can register your own tags using the edd_setup_email_tags action.
This is particularly useful for EDD stores that sell software licenses, subscription products, or anything with custom metadata. If you are building a licensing system, see our guide on setting up EDD software licensing — the license key tag approach below pairs well with that setup. If you store extra information with each payment, you can surface it in the email via a custom tag.
Registering a Custom Tag
add_action( 'edd_setup_email_tags', 'register_my_custom_email_tags' );
function register_my_custom_email_tags() {
edd_add_email_tag( 'license_key', 'The customer license key', 'my_get_license_key_tag' );
edd_add_email_tag( 'access_expires', 'When download access expires', 'my_get_access_expiry_tag' );
}
function my_get_license_key_tag( $payment_id ) {
$license_key = get_post_meta( $payment_id, '_edd_sl_key', true );
return $license_key ? esc_html( $license_key ) : 'N/A';
}
function my_get_access_expiry_tag( $payment_id ) {
$expiry = get_post_meta( $payment_id, '_edd_download_expiry', true );
if ( ! $expiry ) {
return 'Lifetime access';
}
return date_i18n( get_option('date_format'), strtotime( $expiry ) );
}
Once registered, you can use {license_key} and {access_expires} anywhere in the EDD email settings — subject line, header text, body, or footer. This eliminates the need to hard-code dynamic values into your template override.
Method 4: Product-Specific Email Content
One of the most powerful things you can do with EDD’s email system is vary the content based on what was purchased. If you sell both beginner courses and advanced developer tools, the post-purchase messaging for each should look and feel different.
EDD fires edd_email_message with the full $payment_data array. Within that, check $payment_data['downloads'] to see which products are in the order.
add_filter( 'edd_email_message', 'edd_conditional_product_email_content', 10, 2 );
function edd_conditional_product_email_content( $message, $payment_data ) {
if ( empty( $payment_data['downloads'] ) ) {
return $message;
}
$developer_products = array( 42, 87, 113 );
$beginner_products = array( 15, 16, 19 );
$purchased_ids = wp_list_pluck( $payment_data['downloads'], 'id' );
$has_developer = ! empty( array_intersect( $purchased_ids, $developer_products ) );
$has_beginner = ! empty( array_intersect( $purchased_ids, $beginner_products ) );
if ( $has_developer ) {
$message .= '<hr />';
$message .= '<h3>Getting Started as a Developer</h3>';
$message .= '<p>Your download includes full source code and access to our developer Slack channel.</p>';
}
if ( $has_beginner ) {
$message .= '<hr />';
$message .= '<h3>Welcome — Here\'s Where to Start</h3>';
$message .= '<p>Start with Module 1 of the course. <a href="' . esc_url( home_url('/getting-started/') ) . '">Your learning dashboard is here</a>.</p>';
}
return $message;
}
You can also check product categories instead of specific IDs, which is more maintainable as your catalog grows. Use get_the_terms( $download_id, 'download_category' ) inside the loop to inspect product taxonomy.
Method 5: Building a Custom Email Type
Beyond purchase receipts, you may want to send entirely custom transactional emails triggered by your own plugin logic — renewal reminders, license expiration warnings, or download limit notifications. For stores using subscriptions, our EDD recurring payments guide covers the subscription lifecycle where these custom emails are most valuable. EDD provides the EDD_Emails class for this purpose.
Sending Custom Emails via EDD’s Infrastructure
function send_renewal_reminder( $customer_id, $payment_id ) {
$customer = new EDD_Customer( $customer_id );
if ( ! $customer || empty( $customer->email ) ) {
return;
}
EDD()->emails->__set( 'from_name', get_bloginfo('name') );
EDD()->emails->__set( 'from_address', get_option('admin_email') );
EDD()->emails->__set( 'heading', 'Your License Is Expiring Soon' );
$subject = sprintf(
'Action needed: Your %s license expires in 7 days',
get_bloginfo('name')
);
$message = '<p>Hello ' . esc_html( $customer->name ) . ',</p>';
$message .= '<p>Your software license expires in 7 days. Renew now to keep updates and support.</p>';
$message .= '<p><a href="' . esc_url( home_url('/renew/?payment=' . $payment_id) ) . '" ';
$message .= 'style="background:#0073aa;color:#fff;padding:12px 24px;text-decoration:none;border-radius:4px;">';
$message .= 'Renew My License</a></p>';
EDD()->emails->send( $customer->email, $subject, $message );
}
This uses EDD’s email infrastructure, so the message gets wrapped in your customized template automatically. Your logo, color scheme, and footer text all appear as configured — without duplicating any templating code.
Inline CSS and Email Client Compatibility
One pain point that catches many developers: email clients strip <style> blocks. Gmail ignores anything inside a <style> tag. All styling must be written inline on each element.
Best Practices for EDD Email CSS
- Write all CSS inline:
style="color: #333; font-size: 16px;" - Use table-based layouts for consistent rendering across Outlook and Gmail
- Set explicit widths on all table cells and images
- Avoid CSS shorthand — use
font-size,font-family,font-weightas separate properties - Test in Litmus or Email on Acid before deploying
- Use system fonts (
Arial, sans-serif) to avoid web font loading issues
If you want to write normal CSS and auto-inline it, consider using a PHP library like tijsverkoyen/CssToInlineStyles. Hook it into edd_email_message and pass the body through the inliner before the email sends.
Testing Your Email Customizations
EDD includes a built-in email preview tool. Navigate to Downloads > Settings > Emails and click the “Preview” button on the purchase receipt. This renders the full email in the browser using a sample payment, which is the fastest way to iterate on template changes without sending real emails.
Triggering Test Emails via WP-CLI
# Send a test purchase receipt for payment ID 1234
wp eval "edd_trigger_purchase_receipt(1234);"
# Send to a specific address to verify delivery
wp eval "
\$payment = new EDD_Payment(1234);
EDD()->emails->send(
'[email protected]',
'Test Receipt',
edd_get_purchase_receipt_email_content(\$payment->ID, array())
);
"
Use Mailtrap in your development environment to catch all outgoing emails without sending them to real inboxes. Configure it via the WP Mail SMTP plugin.
Organizing Customizations as a Must-Use Plugin
If you are managing multiple EDD email customizations, package them in a must-use plugin rather than scattering code across your theme’s functions.php. This keeps your logic organized, theme-independent, and easier to maintain or hand off to another developer.
<?php
/**
* Plugin Name: My Store Email Customizations
* Description: Custom EDD email templates, hooks, and tags.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'Easy_Digital_Downloads' ) ) {
return;
}
require_once plugin_dir_path( __FILE__ ) . 'includes/email-tags.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/email-filters.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/email-conditional.php';
Place this plugin file in wp-content/mu-plugins/my-store-emails.php. WordPress loads mu-plugins on every request automatically, and they cannot be accidentally deactivated from the plugins screen. Each category of customization lives in its own include — easy to locate, debug, and extend independently.
Which Method Should You Use?
| Customization Goal | Recommended Approach |
|---|---|
| Change overall layout and branding | Template file override in theme |
| Add or modify specific content blocks | edd_email_message filter |
| Change header or footer HTML | edd_email_header_output / edd_email_footer_output |
| Add dynamic placeholders | Custom tags via edd_setup_email_tags |
| Vary content per product purchased | edd_email_message + $payment_data check |
| Send a custom transactional email type | EDD()->emails->send() directly |
EDD’s email system is more extensible than most developers realize. The hooks and template overrides built into the free plugin cover the vast majority of real-world use cases. You don’t need a premium extension for basic branding, conditional content, or custom email types — the five methods above handle all of it.
Take Your EDD Store Further
Customizing transactional emails is one piece of a well-optimized EDD store. If you are looking to extend EDD with custom checkout flows, licensing logic, subscription management, or third-party service integrations, our team specializes in EDD-specific development. Get in touch to discuss your project requirements.
