Skip to content

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.


  1. ld_dashboard_send_email( $template_id, $email, $args ) is called.
  2. LD_Dashboard_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.

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}

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

Merge Tag Default Value Description
{site_name} WordPress site title Site name from get_bloginfo('name')
{site_url} WordPress home URL home_url()
{admin_email} WordPress admin email get_option('admin_email')
{admin_url} WordPress admin URL admin_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 date Formatted 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
)
);

Template ID Subject Template File
instructor_application_admin New Instructor Registered - {user_name} instructor/application-admin-notification.php
instructor_approved Your Instructor Application Approved instructor/application-approved.php
instructor_rejected Your Instructor Application Status instructor/application-rejected.php
student_course_email Message from {instructor_name} student/course-email.php
student_invitation You’re Invited to {course_name} student/invitation.php
withdrawal_request_admin New Withdrawal Request from {instructor_name} withdrawal/request-admin-notification.php
withdrawal_approved Your Withdrawal Request Approved withdrawal/request-approved.php
withdrawal_rejected Your Withdrawal Request Status withdrawal/request-rejected.php
new_message_notification New message from {sender_name} messaging/new-message.php
announcement_notification New Announcement: {announcement_title} student/announcement-notification.php

// 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 );
// 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 );
} );
// 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;
} );
// 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';
} );
// 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 );
// 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 );
// 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,
)
);

The plugin uses a database-backed email queue for bulk sends (instructor email broadcasts). The queue is stored in {prefix}ld_dashboard_email_queue.

Queue table columns:

Column Description
id Auto-increment primary key
user_id Sender user ID
recipient_email Recipient email address
recipient_name Recipient display name
email_subject Email subject
email_message Full HTML email body
headers Serialized headers array
template_id Template ID used to generate this email
status pending, sent, or failed
attempts Number of send attempts
max_attempts Maximum retries (default: 3)
error_message Last error message if failed
scheduled_at When the email is scheduled to send
sent_at Timestamp of successful send
created_at Queue 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.


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_dashboard_email_header and ld_dashboard_email_footer filters (see above).


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.