Laravel mail: pass string instead of view - php

I want to send a confirmation e-mail using laravel.
The laravel Mail::send() function only seems to accept a path to a file on the system.
The problem is that my mailtemplates are stored in the database and not in a file on the system.
How can I pass plain content to the email?
Example:
$content = "Hi,welcome user!";
Mail::send($content,$data,function(){});

update on 7/20/2022: For more current versions of Laravel, the setBody() method in the Mail::send() example below has been replaced with the text() or html() methods.
update: In Laravel 5 you can use raw instead:
Mail::raw('Hi, welcome user!', function ($message) {
$message->to(..)
->subject(..);
});
This is how you do it:
Mail::send([], [], function ($message) {
$message->to(..)
->subject(..)
// here comes what you want
->setBody('Hi, welcome user!'); // assuming text/plain
// or:
->setBody('<h1>Hi, welcome user!</h1>', 'text/html'); // for HTML rich messages
});

For Html emails
Mail::send(array(), array(), function ($message) use ($html) {
$message->to(..)
->subject(..)
->from(..)
->setBody($html, 'text/html');
});

It is not directly related to the question, but for the ones that search for setting the plain text version of your email while keeping the custom HTML version, you can use this example :
Mail::raw([], function($message) {
$message->from('contact#company.com', 'Company name');
$message->to('johndoe#gmail.com');
$message->subject('5% off all our website');
$message->setBody( '<html><h1>5% off its awesome</h1><p>Go get it now !</p></html>', 'text/html' );
$message->addPart("5% off its awesome\n\nGo get it now!", 'text/plain');
});
If you would ask "but why not set first argument as plain text ?", I made a test and it only takes the html part, ignoring the raw part.
If you need to use additional variable, the anonymous function will need you to use use() statement as following :
Mail::raw([], function($message) use($html, $plain, $to, $subject, $formEmail, $formName){
$message->from($fromEmail, $fromName);
$message->to($to);
$message->subject($subject);
$message->setBody($html, 'text/html' ); // dont miss the '<html></html>' or your spam score will increase !
$message->addPart($plain, 'text/plain');
});
Hope it helps you folks.

The Mailer class passes a string to addContent which via various other methods calls views->make(). As a result passing a string of content directly won't work as it'll try and load a view by that name.
What you'll need to do is create a view which simply echos $content
// mail-template.php
<?php echo $content; ?>
And then insert your string into that view at runtime.
$content = "Hi,welcome user!";
$data = [
'content' => $content
];
Mail::send('mail-template', $data, function() { });

I had a similar issue where the HTML and/or plain text of my email were not built by a view and I didn't want to create a dummy view for them (as proposed by #Matthew Odedoyin).
As others have commented, you can use $this->html() to set the HTML content of the message, but what if you want your email to have both HTML and plain text content?
Unfortunately $this->text() only takes a view, but I got around this by using:
$this->text(new HtmlString('Here is the plain text content'));
Which renders the content of the HTMLString instead of the view.

try
public function build()
{
$message = 'Hi,welcome user!'
return $this->html($message)->subject($message);
}

as you know
Only mailables may be queued.
meaning, if you use ShouldQueue interface
1) first, you should always do
php artisan queue:restart
2) second, in your mailable you can use html method (tested in laravel 5.8)
public function build(): self
{
return $this
->html('
<html>
<body>
ForwardEmail
</body>
</html>
')
->subject(config('app.name') . ' ' . 'email forwarded')
->attachData($this->content, 'email.eml', [
'mime' => 'application/eml',
]);
}

If you were using mailables. You can do something like this in the build method :
public function build()
{
return $this->view('email')
->with(['html'=>'This is the message']);
}
And you just go ahead and create the blade view email.blade.php in your resource folder.
Then in the blade you can reference your string using laravel blade syntax
<html>
<body>
{{$html}}
</body>
</html>
or
<html>
<body>
{!!$html!!}
</body>
</html>
If your raw text contains HTML mark up
I hope this works for those who have templates stored in the database and wants to take advantage of the Mailables class in Laravel.

To send raw html, text etc using Laravel Mailables you can
override Mailable->send() in your Mailable and in there, use the method in previous responses:
send([], [], function($message){ $message->setBody() } )
No need to call $this->view() at your build function at all.

NOTE: Below answer is for those who are looking for a flexible approach. i,e (with or without laravel template)
With Template
$payload['message'] = View::make('emails.test-mail',$data)->render();
Without Template
$payload['message'] = "lorem ipsum";
Mail::raw([], function ($mail) use ($payload) {
$mail->from($payload['from_email'])
->to($payload['to'])
->setBody($payload['message'], 'text/html')
->cc($payload['cc'])
->bcc($payload['bcc'])
->subject($payload['subject']);
foreach ($payload['attachments'] as $file){
$mail->attach($file);
}
});

This can be accomplished within a Mailable implementation, with plain text and html content parts:
public function build() {
// Text and html content sections we wish to use in place of view output
$bodyHtml = ...
$bodyText = ...
// Internally, Mailer::renderView($view) interprets $view as the name of a blade template
// unless, instead of string, it is set to an object implementing Htmlable,
// in which case it returns the result $view->toHtml()
$htmlViewAlternative = new class($bodyHtml) implements Htmlable {
protected string $html;
public function __construct($html) {
$this->html = $html;
}
public function toHtml(): string {
return $this->html;
}
};
// We can now set both the html and text content sections without
// involving blade templates. One minor hitch is the Mailable::view($view)
// documents $view as being a string, which is incorrect if you follow
// the convoluted downstream logic.
/** #noinspection PhpParamsInspection */
return $this
->to(...)
->from(...)
->subject(...)
->view([
'html' => $htmlViewAlternative,
'raw' => $bodyText
]);
}

Laravel mailable now has an ->html() function to be used instead of ->view() and works both with o without ->text()

laravel 9 has built in function to send HTML without view. Here is the example:
\Illuminate\Support\Facades\Mail::html($content, function ($message) {
$message->to("email#example.com")
->subject("Test dev 4")
->from("email#example.com");
});
and also if we use accepted answer will return:
Symfony\Component\Mime\Message::setBody(): Argument #1 ($body) must be
of type ?Symfony\Component\Mime\Part\AbstractPart, string given,
called in
/Users/yaskur/Sites/laravel/mail-builder/vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php
on line 23
It's happened because laravel use new library to send email. Previously, use Swiftmailer and now use Symfony Mailer. To send HTML email without view you can also use below code:
Mail::raw("", function ($message) use ($content) {
$body = new \Symfony\Component\Mime\Part\TextPart($content);
$message->to("dyas#example.com")
->subject("Test dev")
->from("no-reply#example.com")
->setBody($body);
});

Related

Return HTML code inside PHP function return

I am very new to PHP and I was handed over a project that has some existing PHP code.
I was asked to add hyperlink to the email address, so I want to know how to add HTML code in the function return.
public function mail()
{
return (new Mail)
->subject('Welcome')
->message('Send questions to', xxx#email.com);
}
Let's assume that everything in the Mail class works. Both of the subject and message methods return the strings inside the brackets.
Can anyone tell me if this code works? I can't run the program for some reason
Usually we write like this
public function returnHtml {
echo "//and here we write html code"
//for example
echo "<h1> hi<h1>"
}
also you can try something like this
function TestBlockHTML ($replStr) {
return <<<HTML
<html>
<body><h1>{$replStr}</h1>
</body>
</html>
HTML;
}

PhpUnit Test - Asserting variables and twig template content

I've defined a simple email controller based on this tutorial:
https://symfony.com/doc/3.4/email.html
So the php file code is:
<?php
namespace AppBundle\Controller;
use Symfony\Component\HttpKernel\Tests\Controller;
class SendEmailController extends Controller
{
public function indexAction($originlEmail, $destinationEmail1, $destinationEmail2, $name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Email Title'))
->setFrom($originlEmail)
->setTo($destinationEmail1, $destinationEmail2)
->setBody(
$this->renderView(
'emails/send-email.html.twig',
array('name' => $name)
),
'text/html'
);
$mailer->send($message);
return $this->render(...);
}
}
And the .twig template code is:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<h3>This is an email!</h3>
<p>Hi {{ name }}, this is an email! </p>
</body>
</html>
Now, following this tutorial https://symfony.com/doc/3.4/email/testing.html I've created a PhpUnit testing class:
<?php
namespace tests\AppBundle\Controller;
use AppBundle\Controller\SendMyEmailController;
class SendCustomerEmailControllerTest extends PHPUnit_Framework_TestCase
{
public function testMailIsSentAndContentIsCorrect()
{
$client = static:: createClient();
$client->enableProfiler();
$crawler = $client->request('POST', 'path/to/above/action');
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
$this->assertSame(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
$this->assertInstanceOf('Swift_Message', $message);
$this->assertSame('My Email Title', $message->getSubject());
$this->assertSame($originEmail, key($message->getFrom()));
$this->assertSame($destinationEmail1, $destinationEmail2, key($message->getTo()));
##Asserting template content???
$this->assertSame(?????, $message->getBody()
);
}
}
Then, the problems I have are:
Where and how should I define (in the test) variables like $originEmail, given the fact that in the "assertSame..." line, I get the error "Undefined variable $originEmail".
Is correct the syntax (considering they are more than one variable) of the line $this->assertSame($destinationEmail1, $destinationEmail2, key($message->getTo())); ?
How can I assert the body content, if instead of a defined text in the .php controller, it is showed in the .twig template (in addition, by using a variable like {{ name }}?
Regarding 1)
How I would go about this depends on how this information is passed into the action. If it's not part of the request, I would just assert against the expected value by doing something like:
$this->assertSame('expected#email.com', $message->getFrom());
If it's part of the route I would assign a local variable in the test and then pass it into the route, maybe like this:
$originalEmail = 'expected#email.com';
$path = sprintf('/path/to/action/%s', $originalEmail);
$client->request('POST', $path);
...
$this->assertSame($originalEmail, $message->getFrom());
If it's part of the POST-data:
$data = [
'originalEmail' => 'expected#email.com',
];
$client->request('POST', '/path/to/action', $data);
...
$this->assertSame($data['originalEmail'], $message->getFrom());
Regarding 2)
This will not work as intended, but you can do something like this (if the email is a value in the array):
$this->assertContains($destinationEmail1, $message->getTo());
Or like this if it's a key (like in your example):
$this->assertArrayHasKey($destinationEmail1, $message->getTo());
Regarding 3)
If you want to compare that the body matches an example you might have to store a reference output in a file and then compare the contents:
$this->assertSame(
file_get_contents(__DIR__ . '/emails/reference_output.html'),
$message->getBody()
);
In that case you have to be careful your email body contains the same name as your reference output.
Alternatively you can also use the crawler to look only for some html elements and their content, e.g. check if the header <h3>This is an email!</h3> is in there:
$crawler = $client->request('POST', '/path/to/action');
$this->assertSame(
'This is an email!',
$crawler->filter('h3')->first()->text()
);
// alternative:
$this->assertGreaterThan(
0,
$crawler->filter('h3:contains("This is an email!")')->count()
);

Preview Mail Notification in browser

With Laravel, according to the documentation, I can return a Mailable via a controller to display it in the browser. It helps to preview mails.
Is there a way to preview Mail Notifications in browser?
I tried:
return (new MyNotification())->toMail($some_user);
But it does not work:
The Response content must be a string or object implementing __toString(), "object" given.
In your controller's function :
$message = (new \App\Notifications\MyNotification())->toMail('example#gmail.com');
$markdown = new \Illuminate\Mail\Markdown(view(), config('mail.markdown'));
return $markdown->render('vendor.notifications.email', $message->data());
Just change the name of the notification class (and also pass arguments if necessary) and hit the url in your browser to see the preview.
You can't render Notification. You can render Mailable that you use in toMail(). For example if that Mailable is called SomeMailable:
public function toMail($user)
{
return (new SomeMailable($user))->to($user->email);
}
Then you can render the Mailable with:
return new SomeMailable($some_user);
In Laravel 5.8 you can now preview it just like you would a Mailable.
Route::get('mail-preview', function () {
return (new MyNotification())->toMail($some_user);
});
More details here:
https://sampo.co.uk/blog/previewing-mail-notifications-in-laravel-just-got-easier
For me, with the particular notification that I wanted to preview toMail() method required a Notifiable instance rather than just an email address, so the following code was what worked for me:
$notification = new \Illuminate\Auth\Notifications\VerifyEmail();
$user = \App\User::where('email', 'example#gmail.com')->first(); // Model with Notifiable trait
$message = $notification->toMail($user);
$markdown = new \Illuminate\Mail\Markdown(view(), config('mail.markdown'));
return $markdown->render('vendor.notifications.email', $message->toArray());
Try this (Test pass after Laravel 5.6)
$message = (new \App\Notifications\YourNotification()->toMail($notifiable);
return app()->make(\Illuminate\Mail\Markdown::class)->render($message->markdown, $message->data());

Mailgun sending email laravel

i use mailgun and the setting is done and i've test it and work, but i dont understand why i can't send email without array, here i tried using array but idk why it's error said Undefined variable: data
public function kirim(Request $request){
$data = array(
'email_address'=>$request->email_address,
'cc'=>$request->cc,
'subject'=>$request->subject,
'keterangantambahan'=>$request->keterangantambahan
);
Mail::send('laporan.kirim', $data, function($message) {
$message->from('christian7andrew#gmail.com', 'PuraBox');
$message->to($data['email_address']);
});
return redirect('/');
}
any idea how to use array corectly ??
Use a use.
Looks like you are using a php version which supports closures
Mail::send('laporan.kirim', $data, function($message) use ($data) {
$message->from('christian7andrew#gmail.com', 'PuraBox');
$message->to($data['email_address']);
});
The second parameter of the send() method is to set mail options. Does not place the variable inside the function body.
The use puts variables into the body of the function

Laravel 5.1: How to pass HTML Body and Text Body without using view?

I'm trying to send emails in laravel5.1 and found that Mail:Send used view templates like below:
Mail::send(['html.view', 'text.view'], $data, $callback);
Problem is I have my ready to send HTML body and TEXT body are coming from database. How to set html view and text view if content coming from database like below:
$html_body = $row['Html_Body']; // holds html content
$text_body = $row['Text_Body']; // holds text content
Thanks.
you can use :
Mail::send(['text' => $text_body], $data, $callback);
you can pass your data to your view like this
$data = [];
$data['Html_Body'];
$data['Text_Body'];
\Mail::send('html.view', $data , function($message)
{
$message->to($email)->subject($subject);
});
and use the data you passed to the view as variables
$Html_Body;
$Text_Body;
$mailBody = View::make('my_mail', ['name' => 'fknight']);
$contents = (string) $mailBody;
// or
$contents = $mailBody->render();
this is the better way instead of using plain html in variable
To attach text/plain message we can use addParts method like below
$message->to($email_details['to'])
->subject($email_details['subject'])
->from($email_details['from'])
->setBody($email_details['html'], 'text/html');
/* add alternative parts with addPart()*/
$message->addPart($email_details['text'], 'text/plain');

Categories