Email customisation
Recipes for changing the MagicAuth email. From, subject, body, and routing through a transactional ESP.
The full email pipeline is exposed via filters. Each step takes the previous step's output, so you can layer customisations: change the From address, append a footer, then route the final message through Postmark, all without forking a template.
For the per-filter reference see Hooks / Email pipeline. This page is recipes.
Headers
Change the From address
add_filter( 'magicauth_email_from', function ( $from, $user ) {
return [ 'Acme Sign-in', 'no-reply@acme.com' ];
}, 10, 2 );Returns a [$display_name, $address] array. Both pieces are header-sanitised by MagicAuth before they hit the headers, so no need to escape yourself.
Add a Reply-To header
add_filter( 'magicauth_email_headers', function ( $headers, $user ) {
$headers[] = 'Reply-To: support@acme.com';
return $headers;
}, 10, 2 );Change the subject
The default subject is {XXX-XXX} is your {Company}-code. Putting the code in the subject means it appears in inbox previews and lock-screen notifications, which significantly speeds up cross-device sign-in.
If you want a more conventional subject:
add_filter( 'magicauth_email_subject', function ( $subject, $user, $args ) {
return sprintf( 'Sign in to %s', $args['company_name'] );
}, 10, 3 );Body
Append a footer
The cleanest place to layer in additions is magicauth_email_html and magicauth_email_plaintext:
add_filter( 'magicauth_email_html', function ( $html, $args ) {
$footer = '<p style="font-size:12px;color:#888;text-align:center;margin-top:32px">'
. 'Sent by Acme · <a href="https://acme.com/support">Support</a>'
. '</p>';
// Inject just before the closing </body>.
return preg_replace( '#</body>#i', $footer . '</body>', $html, 1 );
}, 10, 2 );
add_filter( 'magicauth_email_plaintext', function ( $text, $args ) {
return $text . "\n\n--\nSent by Acme · https://acme.com/support";
}, 10, 2 );Inject extra context into a custom template
If you've copied a template into your-theme/magicauth/ and want access to data MagicAuth doesn't pass by default, add it to the args array and reference it in your template:
add_filter( 'magicauth_email_template_args', function ( $args, $user ) {
$args['support_url'] = 'https://acme.com/support';
$args['marketing_optin'] = (bool) get_user_meta( $user->ID, 'acme_marketing', true );
return $args;
}, 10, 2 );In your-theme/magicauth/email-magic-link.php you can now use $support_url and $marketing_optin as locals.
Combine template overrides with a footer
Combining template overrides and post-render filters is fine. If you've replaced the HTML template wholesale, you might still want the plaintext to come from the default with an appended footer:
// your-theme/magicauth/email-magic-link.php: fully custom HTML.
// no plaintext override.
add_filter( 'magicauth_email_plaintext', function ( $text ) {
return $text . "\n\n--\nSent by Acme.";
} );Routing
Route through a transactional ESP
MagicAuth uses wp_mail by default. For provider-grade deliverability, short-circuit the send and call your provider's API directly.
add_filter( 'magicauth_email_send', function ( $null, $user, $args ) {
$api_token = getenv( 'POSTMARK_TOKEN' );
$response = wp_remote_post( 'https://api.postmarkapp.com/email', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Postmark-Server-Token' => $api_token,
],
'body' => wp_json_encode( [
'From' => 'no-reply@acme.com',
'To' => $user->user_email,
'Subject' => $args['code_display'] . ' is your Acme code',
'HtmlBody' => $args['link'], // Or run your own template render here.
'TextBody' => $args['code'],
'Tag' => 'magicauth',
] ),
'timeout' => 6,
] );
if ( is_wp_error( $response ) ) {
return false;
}
return wp_remote_retrieve_response_code( $response ) < 300;
}, 10, 3 );Returning null (the default) means "let MagicAuth send via wp_mail". Returning any non-null value (including false) means "I handled it". false propagates as a send failure, true as success.
What not to override
Three things in the email carry security guarantees. Render them; do not regenerate or reformat.
- The verify URL (
$link). It carries the verifier secret. - The code (
$code/$code_display). Already normalised. - The disabled-notice email's URL-free body. Adding clickable links re-introduces a phishing vector.
Disabled-notice email
The "your magic-link sign-in is disabled" email has its own filter set.
Different subject
add_filter( 'magicauth_disabled_notice_subject', function ( $subject, $user, $args ) {
return 'Your sign-in method changed';
}, 10, 3 );Different body
add_filter( 'magicauth_disabled_notice_html', function ( $html, $args ) {
// Append something, but do not add clickable URLs here.
return $html;
}, 10, 2 );