Developer customizing EDD email templates with PHP code on dark monitors

How to Customize EDD Email Templates (Complete Developer Guide)

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:

  1. yourtheme/edd_templates/emails/
  2. yourtheme/edd/emails/
  3. 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

FilterWhat It ControlsDefault Value
edd_email_header_outputFull header HTML stringEDD header template
edd_email_footer_outputFull footer HTML stringEDD footer template
edd_email_messageThe main body content stringPurchase receipt text
edd_purchase_receipt_email_subjectSubject line for purchase receipts“Purchase Receipt”
edd_get_email_template_tagsArray of available template tagsBuilt-in EDD tags
edd_email_from_nameSender display nameSite name from settings
edd_email_from_addressSender email addressAdmin 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-weight as 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 GoalRecommended Approach
Change overall layout and brandingTemplate file override in theme
Add or modify specific content blocksedd_email_message filter
Change header or footer HTMLedd_email_header_output / edd_email_footer_output
Add dynamic placeholdersCustom tags via edd_setup_email_tags
Vary content per product purchasededd_email_message + $payment_data check
Send a custom transactional email typeEDD()->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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top