framework: Variables not working inside Markdown Mailable template files

  • Laravel Version: 5.4.16
  • PHP Version: 7.0.15
  • Database Driver & Version: MySQL 5.7.17

Description:

Variables aren’t working when using them inside the Markdown Mailable templates files. You get a ‘undefined variable’ message.

Steps To Reproduce:

When following the guide on https://laravel.com/docs/5.4/mail#markdown-mailables to create a Markdown email you can customise the template files by using the code below:

php artisan vendor:publish --tag=laravel-mail

When adding a variable into the Mailable, the variable isn’t available on the template files to e.g. be used inside the header or footer of the message.

E.g. Mailable

class ReviewInvite extends Mailable
{
    use Queueable, SerializesModels;

    public $visit;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Visit $visit)
    {
        $this->visit = $visit;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->markdown('emails.review-invite');
    }
}

We can use the $visit variable inside the emails.review-invite file, but when using the $visit variable inside any of the /resources/views/vendor/mail/html files, you get a “Undefined variable: visit” message.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 2
  • Comments: 23 (2 by maintainers)

Most upvoted comments

For anybody looking at providing variables to the templates header and footer here is the solution.

Step 1: Publish the view files using Laravel’s publish tool php artisan vendor:publish --tag=laravel-mail

Step 2: Make a variable available to your Mailable view by making it public (no need to use with method).

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class InsuranceBought extends Mailable
{
    use Queueable, SerializesModels;
    public $header_url, $header_title;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->header_url = 'https://example.com';
        $this->header_title = 'My title';
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('example@example.com')
            ->subject('Insurance bought for tripname trip!')
            ->markdown('trips.email.students');
    }
}

Step 3: Update the vendor/mail/html/message.blade.php and vendor/mail/markdown/message.blade.php files to be the following:

@component('mail::layout')

    {{-- Header --}}
    @slot('header')
        @component('mail::header', ['url' => isset($header_url) ? $header_url : config('app.url')])
         {{ isset($header_title) ? $header_title : config('app.name') }}
        @endcomponent
    @endslot

    {{-- Body --}}
    {{ $slot }}

    {{-- Subcopy --}}
    @if (isset($subcopy))
        @slot('subcopy')
            @component('mail::subcopy')
                {{ $subcopy }}
            @endcomponent
        @endslot
    @endif

    {{-- Footer --}}
    @slot('footer')
        @component('mail::footer')
            &copy; {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
        @endcomponent
    @endslot
@endcomponent

Last step is to give the blade component access to the variable as explained by @themsaid

The following is your mailable view file:

@component('mail::message', ['header_url' => $header_url, 'header_title' => $header_title])

Hey {{ $user->first_name }},

Here is the message you are sending.

@component('mail::button', ['url' => $url])
Create Account
@endcomponent

Thanks,<br>
{{ $account->name }}
@endcomponent

For anyone who faced this problem. Pass the second value as array please

@Qbixx Pass data with with($name,$value), and $value just has to be an array 😃

For example: in class Welcome extends Mailable


    public $customer;

    public function __construct(User $user)
    {
        //
        $this->customer = $user->toArray();
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {

        return $this->markdown('emails.welcome')
                    ->with('customer', $this->customer);
    }

PLEASE note: if you have a variable that you made it a global via view composer, in your CSP, then it gonna override your array.

Dear @themsaid it seems like blade components don’t have a REAL different scope than normal views after all?! I mean it got effected with view composer, anyways, thanks!

Ok, typical as most Laravel community issues people just throw around ideas without following the stack and investigating them selfs - and perhaps learning along the way… Also if your giving example dont just paste code from your own use-case / application. Make it generic and readable for the community. This is Open Source Software.

@themsaid Your answer is actually incorrect. Sorry. May you please update it as it’s the highest comment on this thread that Google picks up.

Solution

return (new MailMessage)
    ->subject('Hello world')
    ->markdown('emails.hello-world', [
        'firstName' => $notifiable->firstName,
        'lastName' => $notifiable->lastName,              
    ]);

You may pass view data as second param to markdown() function.

Thanks.

The key is to use public for the data you’re passing in.

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class InsuranceBought extends Mailable
{
    use Queueable, SerializesModels;
    public $data;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('example@example.com')
            ->subject('Insurance bought for tripname trip!')
            ->markdown('trips.email.students')
            ->with(['data', $this->data]);
    }
}

And then for sending:

$data = array('test' => 'bla');
Mail::to($student)->send(new InsuranceBought($data));

@mkwsra thanks! actually I was doing it right. I discovered that when I stopped queueing the mail and it got sent without issues. Then I remember I should probably restart horizon when I change the code. And yes, I do have to. 🤦🏻️ 😂️

Hi everyone!

I’m trying to add a variable to the /vendor/mail/html/message.blade.php template kind of like how @qbixx was describing. I’m sending markdown emails through Laravel’s Notifications, but I always get undefined variable when I try and pass it to the main message.blade.php template. Does anyone have any suggestions? Here is my message.blade.php:

@component('mail::layout')
    {{-- Header --}}
    @slot('header')
        @component('mail::header', ['url' => config('app.url')])
          Test
          {{ $my_testing_var }}
        @endcomponent
    @endslot

    {{-- Body --}}
    {{ $slot }}

    {{-- Subcopy --}}
    @isset($subcopy)
        @slot('subcopy')
            @component('mail::subcopy')
                {{ $subcopy }}
            @endcomponent
        @endslot
    @endisset

    {{-- Footer --}}
    @slot('footer')
        @component('mail::footer')
            &copy; {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
        @endcomponent
    @endslot
@endcomponent

and here is my toMail() function in my notification:

public function toMail($notifiable)
    {
      return (new MailMessage)
        ->subject( 'testing' )
        ->markdown('mail.test', [
          'test_user' => 'test',
        ])->with( 'my_testing_var', [ 'my_testing_var' => 'testing' ] );
    }

As you can see I’m using with() on the MailMessage, but this still results in an undefined variable upon the notification call.

Anyone have any ideas?

Thanks!

Yes all variables are only available in the mailable view, this is by design since mail templates uses components, a blade component doesn’t have the same scope as its main slot.

Check: https://laravel.com/docs/5.4/blade#components-and-slots

Controller

$email = new welcomemail($user);
Mail::to(Auth::user()->email)->send($email);

mail controller

namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class welcomemail extends Mailable
{
    use Queueable, SerializesModels;

    public $stu;
    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($stu)
    {
        $this->stu = $stu;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->markdown('emails.welcome')->with($this->stu);
    }
}

view

@component('mail::message')
# Introduction

The body of your message {{$stu}}.

@component('mail::button', ['url' => ''])
Button Text
@endcomponent

Thanks,<br>
{{ config('app.name') }}
@endcomponent

this works 100% correct

Hi

Does anyone know if you can get blade variables working in the vendor/mail/html/layout.blade.php file ? This is the html file that the markdown appears to be generated in.

I wanted to put a " ---- Please reply above this line —" into the output but it has to be straight after the body tag before any of the components are created and only for emails that are coming from our support ticket system (otherwise I could just code the words directly in the file).

I wanted to use markdown because its easy to create tables and buttons etc but the above code for using ->with() doesn’t seem to work in the actual layout.blade.php file - I wonder if this is what @themsaid was referring to

Yes, i did it… First the Mailable Class `class ContactNew extends Mailable { use Queueable, SerializesModels;

/**
 * Create a new message instance.
 *
 * @return void
 */
public function __construct() {

}

/**
 * Build the message.
 *
 * @return $this
 */
public function build(Request $request) {
	$email = $request->email;

	return $this->from($request->email)
		->markdown('auth.mail.contactNew')
		->with('email', $request->email);
}

}Then... the Blade file@component(‘mail::message’)

Introduction

{{$email}} The body of your message.

@component(‘mail::button’, [‘url’ => ‘’]) Button Text @endcomponent

Thanks,
{{ config(‘app.name’) }} @endcomponent `

Check Passing Additional Data To Components https://laravel.com/docs/5.4/blade#components-and-slots, you can use that to pass data to the main layout.

Closing since it’s not a bug report but please feel free to ping me if you need further help.

Hello,

I’m using laravel 9 and I can’t pass variable to the mailable markdown templates.

I tried several solutions presented here with no success:

Here is my class:

class Emailer extends Mailable
{
    use Queueable, SerializesModels;

    public $data;
    public $pedigree;


    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(array $email)
    {
        $this->data = $email;
    }


    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        $this->pedigree= ['name' => "test"] ;

        return $this->from($this->data['from'])
            ->subject($this->data['subject'])
            ->markdown('mail::message', ['slot'=> $this->data['email_content']])
            ->with('pedigree', $this->pedigree);
    }
}

Than my template :

@component('mail::layout')
    {{-- Header --}}
    @slot('header')
        @component('mail::header', ['url' => config('app.url')])
            {{ config('app.name') }}
        @endcomponent
    @endslot

    {{-- Body --}}
    {{ $slot }}

    @isset($pedigree)
        @component('mail::table')
            {{$pedigree}}
        @endcomponent
    @endisset


    {{-- Footer --}}
    @slot('footer')
        @component('mail::footer')
            © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
        @endcomponent
    @endslot
@endcomponent

And my variable $pedigree is always undefined… Any idea ? Thanks, Denis

You need to pass it in a few places.

  1. First null coalesce the variables that you may want to use in resources/views/vendor/mail/html/message.blade.php
  2. Next pass your variables in the markdown template you’re using. resources/views/emails/test.blade.php
  3. Finally, use the variables in your header template. (The null coalescing will protect or provide defaults, for instance default header text)
  4. Make sure you pass the variables via public property on the mailer class or in the data array explicitly

resources/views/vendor/mail/html/message.blade.php

@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', [
'url' => config('app.url'),
'headerTitle' => $headerTitle ?? 'Default Title',
'headerSubtitle' => $headerSubtitle ?? 'Default Subtitle',
])
{{ config('app.name') }}
@endcomponent
@endslot

{{-- Body --}}
{{ $slot }}

{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset

{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent

resources/views/emails/test.blade.php

@component('mail::message', ['headerTitle' => $headerTitle, 'headerSubtitle' => $headerSubtitle])
# Please Review This 

Thanks,<br>
{{ config('app.name') }}
@endcomponent

resources/views/vendor/mail/html/header.blade.php

<tr>
    <td class="header">
        <table class="theme-header" align="center" style="margin: 0 auto;">
            <tbody>
                <tr>
                    <td style="width: 50%;">
                        <a href="{{ $url }}" style="display: inline-block;">
                            @if (trim($slot) === 'Laravel')
                            <img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
                            @endif
                        </a>
                    </td>
                    <td style="width: 50%;">
                        <h1>{{ $headerTitle }}</h1>
                        <p class="sub">{{ $headerSubtitle }}</p>
                    </td>
                </tr>
            </tbody>
        </table>
    </td>
</tr>

Ok, typical as most Laravel community issues people just throw around ideas without following the stack and investigating them selfs - and perhaps learning along the way… Also if your giving example dont just paste code from your own use-case / application. Make it generic and readable for the community. This is Open Source Software.

@themsaid Your answer is actually incorrect. Sorry. May you please update it as it’s the highest comment on this thread that Google picks up.

Solution

return (new MailMessage)
    ->subject('Hello world')
    ->markdown('emails.hello-world', [
        'firstName' => $notifiable->firstName,
        'lastName' => $notifiable->lastName,              
    ]);

You may pass view data as second param to markdown() function.

Thanks.

fixed my error