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
ld<em>dashboard</em>send<em>email( $template</em>id, $email, $args )is called.LD<em>Dashboard</em>Email_Templatelocates the template file (theme override first).- The template file is rendered with output buffering.
- Merge tags are replaced in the output.
- The content is wrapped in
email-header.phpandemail-footer.php. - Filters run on subject, content, headers, and recipient.
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:
- Child theme:
{child-theme}/ld-dashboard/emails/{template-path} - Parent theme:
{parent-theme}/ld-dashboard/emails/{template-path} - 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 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<em>option('admin</em>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
)
);
Registered Email Types
| Template ID | Subject | Template File |
|---|---|---|
instructor<em>application</em>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<em>course</em>email | Message from {instructor_name} | student/course-email.php |
student_invitation | You’re Invited to {course_name} | student/invitation.php |
withdrawal<em>request</em>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<em>message</em>notification | New message from {sender_name} | messaging/new-message.php |
announcement_notification | New 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:
| 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.
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:
- Copy
templates/emails/instructor/application-approved.phpto:my-theme/ld-dashboard/emails/instructor/application-approved.php - 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.
