0

I have a Mailable which accepts two parameters which are used in the generated view when sent to the user. I want to emit an event once the Mailable has sent, however, a parameter should be passed to that event which is the string version of the view that is generated by the Mailable with the variables replaced:

namespace App\Mail;

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

class DummyMail extends Mailable
{
    use Queueable, SerializesModels;

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

    /**
    * Build the message.
    *
    * @return $this
    */
    public function build()
    {
        $this->from('dummy@test.com')->subject('Howdy!')->view('mail.dummy-tpl')->with([
            'title' => $this->title,
            'name' => $this->name
        ]);
    }
}

I have tried:

  • Adding event(new CommunicationEvent($this->render())); before and after the $this->from([...])[...] call in the build function
  • I have tried calling (new DummyMail($this->title, $this->name))->render(); as suggested here. The event is not fired nor is the response of my AJAX request successful, both storage/logs/laravel.log and apache2 logs are not helpful.
  • I have tried calling $this->buildMarkdownView() that should have a key called html which should give me the template, however that throws an error saying 'View [] not found.'.

So, how can I simply return the view that is generated for the email as a string so that it can be passed to the event that I plan on emitting?

Script47
  • 14,230
  • 4
  • 45
  • 66
  • What is it you’re trying to do? Why do you need the mail’s body in an event? What does that event / its listeners do? – Martin Bean Nov 29 '18 at 11:40
  • @MartinBean I've solved the issue as my answer below shows. Essentially I wanted to log all emails being sent out via an event - and yes, I know about the mail events, and yes, I've tried them but the issue I was facing was the rendered HTML template was never in the properties passed to default `MessageSending` or `MessageSent` events. So you couldn't actually create a record of what exactly was sent to the user. – Script47 Nov 29 '18 at 11:43
  • If you look at the source for the [MessageSent](https://github.com/laravel/framework/blob/453317f5b2c93b5312392f36e0574d5be9755d0e/src/Illuminate/Mail/Events/MessageSent.php) event, the first parameter is a `Swift_Message` instance. You could get that [as a string](https://github.com/swiftmailer/swiftmailer/blob/ddd5658c487ff2dd4c6cbfdeeb46505962adb6c1/lib/classes/Swift/Message.php#L139), i.e. `$event->message->toString()`. – Martin Bean Nov 29 '18 at 11:47
  • 1
    @MartinBean I'll give that a go and see what it outputs, one moment. – Script47 Nov 29 '18 at 11:48
  • @MartinBean it gives you the [raw](https://pastebin.com/raw/bEUdsrqY) email output, unsure if it is parsable or how I'd even go about that. – Script47 Nov 29 '18 at 11:58
  • It seems that you can parse the raw email but that might be a complicated process: https://stackoverflow.com/questions/12896/parsing-raw-email-in-php – Script47 Nov 29 '18 at 12:13
  • What is it you’d need to “parse”? You’re wanting to log emails sent? That would give you a text representation on a sent email. – Martin Bean Nov 29 '18 at 12:16
  • @MartinBean I'd just want the generated view's HTML in a way that if needed it can simply be pulled from the db and displayed without hassle. – Script47 Nov 29 '18 at 12:18

1 Answers1

0

So, I've managed to render the template and pass it to the event, it might not be the most "eloquent" solution however, it seems to do the job.

  1. In your Mailable define publicly the view that will be used:

    public $view = 'mail.dummy-tpl';
    
  2. In the build function, after $this->from('dummy@test.com')[...] we need to get the rendered template as a string:

    $tpl = view($this->view, [
        'title' => $this->title,
        'name' => $this->name,             
    ])->render();
    

    I've noticed that you don't need to use the render function to return the rendered template as string however everywhere I looked it was advised to use render, so better to be explicit.

  3. Finally, call the event of your choice using the event helper:

    event(new CommunicationEvent($tpl, $this));
    

With the above changes made, the resultant Mailable class should now be:

<?php

namespace App\Mail;

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

class DummyMail extends Mailable
{
    use Queueable, SerializesModels;

    public $view = 'mail.dummy-tpl';

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

    /**
    * Build the message.
    *
    * @return $this
    */
    public function build()
    {
        $this->from('dummy@test.com')->subject('Howdy!')->with([
            'title' => $this->title,
            'name' => $this->name
        ]);

        $tpl = view($this->view, [
            'title' => $this->title,
            'name' => $this->name,             
        ])->render();

        event(new CommunicationEvent($tpl, $this));        
    }
}

Note: As we have defined the property $view publicly, we no longer need the view chain in the following code:

$this->from('dummy@test.com')->subject('Howdy!')->view('mail.dummy-tpl')->with([
    'title' => $this->title,
    'name' => $this->name
]);`  

Gotchas

Ensure that the $view property is public otherwise you'll get the following error:

Access level to App\Mail\DummyMail::$view must be public (as in class Illuminate\Mail\Mailable)

Script47
  • 14,230
  • 4
  • 45
  • 66