Email Template Customization

Get Started

Email Template Customization

LearnDash Dashboard uses a WooCommerce-style email template system introduced in version 7.5.0. Templates support theme directory overrides, merge tags, and multiple filter hooks.


How the System Works

  1. ld<em>dashboard</em>send<em>email( $template</em>id, $email, $args ) is called.
  2. LD<em>Dashboard</em>Email_Template locates the template file (theme override first).
  3. The template file is rendered with output buffering.
  4. Merge tags are replaced in the output.
  5. The content is wrapped in email-header.php and email-footer.php.
  6. Filters run on subject, content, headers, and recipient.
  7. wp_mail() sends the email.

Template Override via Theme

Copy any template from the plugin’s templates/emails/ directory to your active theme’s ld-dashboard/emails/ directory. The override takes precedence automatically.

Plugin template location:

wp-content/plugins/ld-dashboard/templates/emails/
├── email-header.php
├── email-footer.php
├── email-styles.php
├── instructor/
│   ├── application-admin-notification.php
│   ├── application-approved.php
│   └── application-rejected.php
├── student/
│   ├── course-email.php
│   ├── invitation.php
│   └── announcement-notification.php
├── withdrawal/
│   ├── request-admin-notification.php
│   ├── request-approved.php
│   └── request-rejected.php
└── messaging/
    └── new-message.php

Theme override location:

wp-content/themes/my-theme/ld-dashboard/emails/
└── instructor/
    └── application-approved.php    ← overrides plugin template

Template lookup order:

  1. Child theme: {child-theme}/ld-dashboard/emails/{template-path}
  2. Parent theme: {parent-theme}/ld-dashboard/emails/{template-path}
  3. Plugin: {plugin}/templates/emails/{template-path}

Available Merge Tags

Merge tags are replaced in both the email subject and body. They use curly brace syntax with an underscore-separated name.

Merge TagDefault ValueDescription
{site_name}WordPress site titleSite name from get_bloginfo('name')
{site_url}WordPress home URLhome_url()
{admin_email}WordPress admin emailget<em>option('admin</em>email')
{admin_url}WordPress admin URLadmin_url()
{user_name}""Recipient display name
{user_email}""Recipient email address
{instructor_name}""Instructor display name
{student_name}""Student display name
{course_name}""Course title
{amount}""Commission/payment amount
{date}Current dateFormatted with site date format
{sender_name}""Message sender name (private messaging)
{message_url}""Direct link to the message thread
{announcement_title}""Announcement post title

Pass custom values via the $args array:

ld_dashboard_send_email(
    'instructor_approved',
    $user->user_email,
    array(
        'user_name'       => $user->display_name,
        'instructor_name' => $user->display_name,
        'site_name'       => get_bloginfo( 'name' ),
        'custom_field'    => 'My custom value', // Added via ld_dashboard_email_merge_tags
    )
);

Registered Email Types

Template IDSubjectTemplate File
instructor<em>application</em>adminNew Instructor Registered – {user_name}instructor/application-admin-notification.php
instructor_approvedYour Instructor Application Approvedinstructor/application-approved.php
instructor_rejectedYour Instructor Application Statusinstructor/application-rejected.php
student<em>course</em>emailMessage from {instructor_name}student/course-email.php
student_invitationYou’re Invited to {course_name}student/invitation.php
withdrawal<em>request</em>adminNew Withdrawal Request from {instructor_name}withdrawal/request-admin-notification.php
withdrawal_approvedYour Withdrawal Request Approvedwithdrawal/request-approved.php
withdrawal_rejectedYour Withdrawal Request Statuswithdrawal/request-rejected.php
new<em>message</em>notificationNew message from {sender_name}messaging/new-message.php
announcement_notificationNew Announcement: {announcement_title}student/announcement-notification.php

Filter Hooks

Subject Filters

// Filter subject for ALL emails
add_filter( 'ld_dashboard_email_subject', function( $subject, $template_id, $args ) {
    return '[' . get_bloginfo( 'name' ) . '] ' . $subject;
}, 10, 3 );

// Filter subject for a specific template
add_filter( 'ld_dashboard_email_subject_instructor_approved', function( $subject, $template_id, $args ) {
    return $subject . ' - ' . date( 'Y' );
}, 10, 3 );

Content Filters

// Filter content for ALL emails (before header/footer wrap)
add_filter( 'ld_dashboard_email_content', function( $content, $template_id, $args ) {
    return $content . '<p style="font-size:11px;">Powered by My Site</p>';
}, 10, 3 );

// Filter content for a specific template
add_filter( 'ld_dashboard_email_content_student_invitation', function( $content ) {
    return str_replace( '{custom_promo}', 'Use code WELCOME20 for 20% off', $content );
} );

Header and Footer Filters

// Replace the email header HTML
add_filter( 'ld_dashboard_email_header', function( $header ) {
    return str_replace( 'class="ld-email-header"', 'class="ld-email-header my-custom-header"', $header );
} );

// Append to the email footer
add_filter( 'ld_dashboard_email_footer', function( $footer ) {
    $unsubscribe = '<p><a href="' . esc_url( home_url( '/unsubscribe' ) ) . '">Unsubscribe</a></p>';
    return $unsubscribe . $footer;
} );

Header (SMTP) Filters

// Add CC header for admin approval emails
add_filter( 'ld_dashboard_email_headers_instructor_approved', function( $headers ) {
    $headers[] = 'CC: compliance@example.com';
    return $headers;
} );

// Change From address globally
add_filter( 'ld_dashboard_email_from_address', function( $email ) {
    return 'noreply@example.com';
} );

add_filter( 'ld_dashboard_email_from_name', function( $name ) {
    return get_bloginfo( 'name' ) . ' Notifications';
} );

Recipient Filter

// Redirect all plugin emails in staging environments
add_filter( 'ld_dashboard_email_recipient', function( $to, $template_id, $args ) {
    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        return 'dev@example.com';
    }
    return $to;
}, 10, 3 );

Merge Tag Filter

// Add custom merge tags
add_filter( 'ld_dashboard_email_merge_tags', function( $tags, $content, $args ) {
    $tags['company_name']   = get_option( 'my_company_name', '' );
    $tags['support_email']  = 'support@example.com';
    $tags['current_year']   = date( 'Y' );
    return $tags;
}, 10, 3 );

Add a Custom Email Type

// Register a new email type
add_filter( 'ld_dashboard_email_types', function( $types ) {
    $types['course_completed'] = array(
        'title'    => __( 'Course Completed', 'my-plugin' ),
        'template' => 'student/course-completed.php',
        'subject'  => __( 'Congratulations! You completed {course_name}', 'my-plugin' ),
    );
    return $types;
} );

Then create the template at: my-theme/ld-dashboard/emails/student/course-completed.php

<?php
// $template_args is available — same as $args passed to ld_dashboard_send_email().
$user_name   = isset( $template_args['user_name'] ) ? $template_args['user_name'] : '';
$course_name = isset( $template_args['course_name'] ) ? $template_args['course_name'] : '';
?>
<h2>Well done, <?php echo esc_html( $user_name ); ?>!</h2>
<p>You have successfully completed <strong><?php echo esc_html( $course_name ); ?></strong>.</p>
<p><a href="{site_url}/my-dashboard/" style="background:#2067fa;color:#fff;padding:10px 20px;text-decoration:none;border-radius:4px;">View Your Dashboard</a></p>

Then send it:

ld_dashboard_send_email(
    'course_completed',
    $student->user_email,
    array(
        'user_name'   => $student->display_name,
        'course_name' => $course->post_title,
    )
);

Email Queue System

The plugin uses a database-backed email queue for bulk sends (instructor email broadcasts). The queue is stored in {prefix}ld<em>dashboard</em>email_queue.

Queue table columns:

ColumnDescription
idAuto-increment primary key
user_idSender user ID
recipient_emailRecipient email address
recipient_nameRecipient display name
email_subjectEmail subject
email_messageFull HTML email body
headersSerialized headers array
template_idTemplate ID used to generate this email
statuspending, sent, or failed
attemptsNumber of send attempts
max_attemptsMaximum retries (default: 3)
error_messageLast error message if failed
scheduled_atWhen the email is scheduled to send
sent_atTimestamp of successful send
created_atQueue insertion timestamp

Processing:

A WordPress cron job processes the queue. Emails are processed in batches. Failed sends are retried up to max_attempts times with exponential backoff. The plugin also runs a daily cleanup cron to remove old sent and failed records.


Header and Footer Customization

To customize the shared header and footer that wrap all emails, copy the files to your theme:

my-theme/ld-dashboard/emails/
├── email-header.php    ← wrap start, logo, background
└── email-footer.php    ← copyright, social links, wrap end

The header template receives $email_subject as a local variable (used for the HTML <title> tag).

Both header and footer templates are also filterable via ld<em>dashboard</em>email<em>header and ld</em>dashboard<em>email</em>footer filters (see above).


Enrollment Notification Example

Customize the instructor-approved email to include a direct dashboard link:

  1. Copy templates/emails/instructor/application-approved.php to: my-theme/ld-dashboard/emails/instructor/application-approved.php
  2. Edit the template:
<?php
$user_name     = isset( $template_args['user_name'] ) ? $template_args['user_name'] : '';
$dashboard_url = Ld_Dashboard_Functions::instance()->ld_dashboard_get_url( 'dashboard' );
?>
<h2><?php printf( esc_html__( 'Welcome, %s!', 'ld-dashboard' ), esc_html( $user_name ) ); ?></h2>
<p><?php esc_html_e( 'Your instructor application has been approved.', 'ld-dashboard' ); ?></p>
<p><?php esc_html_e( 'You can now create courses and manage students from your dashboard.', 'ld-dashboard' ); ?></p>
<p style="text-align:center;margin-top:24px;">
    <a href="<?php echo esc_url( $dashboard_url ); ?>"
       style="background:#2067fa;color:#fff;padding:12px 28px;text-decoration:none;border-radius:4px;display:inline-block;">
        <?php esc_html_e( 'Go to Your Dashboard', 'ld-dashboard' ); ?>
    </a>
</p>

The override is detected automatically — no code changes required.

Last updated: March 4, 2026