Ettic Docs
MagicAuthDevelopers

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

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.

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 );

On this page