I want to send email verification when a user signs up with a new Email Address. So at the Register Controller I added this:
public function register(Request $request)
{
if(Session::has('email')){
return Redirect::back()->withErrors(['msg' => 'Email was already sent to you, please check the spam folder too.']);
}else{
$validatedEmail = $request->validate([
'user_input' => 'required|unique:users,usr_email|regex:/(.+)#(.+)\.(.+)/i|max:125|min:3',
],[
'user_input.required' => 'You must enter this field',
'user_input.unique' => 'This email is already registered',
'user_input.regex' => 'This email is not correct',
'user_input.max' => 'Maximum length must be 125 characters',
'user_input.min' => 'Minimum length must be 3 characters',
]);
$register = new NewRegisterMemberWithEmail();
return $register->register();
}
}
So if the email was valid, it will call a helper class NewRegisterMemberWithEmail which goes like this:
class NewRegisterMemberWithEmail
{
public function register()
{
try{
$details = [
'title' => 'Verify email'
];
Mail::to(request()->all()['user_input'])->send(new AuthMail($details));
Session::put('email',request()->all()['user_input']);
return redirect()->route('login.form');
}catch(\PDOException $e){
dd($e);
}
}
}
So it used to work fine and correctly sends the email for verification, but I don't know why it does not send email nowadays.
In fact I have tested this with different mail service providers and for both Yahoo & Gmail the email did not received somehow!
But for local mail service provider based in my country the email was sent properly!
I don't know really what's going on here because the logic seems to be fine...
So if you know, please let me know... I would really really appreciate any idea or suggestion from you guys.
Also here is my AuthMail Class if you want to take a look at:
class AuthMail extends Mailable
{
use Queueable, SerializesModels;
public $details;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($details)
{
$this->details = $details;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->subject('Sitename')->view('emails.AuthMail');
}
}
Once I was faced same problem when I was used Gmail as smtp.
Reason:
when we used our Gmail password directly in smtp settings then due to some Gmail policies it'll be blocked after sometime (months) and stopped email sending.
Solution:
we need to create an app-password from our Gmail security and use that password in smtp settings. below google article will guide:
How to create app-password on gmail
.env smtp setting for laravel:
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=<your-email>
MAIL_PASSWORD=<app-password>
MAIL_ENCRYPTION=tls
I hope that'll help you.
If you use google mail to send email then we have the same problem.
On May 30, 2022 Google stop supporting less secure applications or third party application.
This is I think the reason why your send mail does not work (consider this answer if you use google mail as mail sender)
I was having issues when sending email, especially to gmail accounts. So I have changed my approach and overcome that issue.
Please check my answer below
Laravel Email
Example Mail Class
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Symfony\Component\Mime\Email;
class OrderInfoMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $data;
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$this
->subject('Order Confirmation')
->from('noreply#app.xxx.co.uk', 'XXX Portal')
->view('orders.templates.order-form')
->with([
'name' => $this->data->name,
'sales_representative_name' => $this->data->sales_representative_name,
'sales_representative_phone' => $this->data->sales_representative_phone,
"items" => $this->data->items,
"address" => $this->data->address,
"net" => $this->data->net,
"payment" => $this->data->payment,
"balance" => $this->data->balance,
]);
$this->withSymfonyMessage(function (Email $message) {
$message->getHeaders()->addTextHeader(
'X-Mailer', 'PHP/' . phpversion()
);
});
return $this;
}
}
Usage
$email = 'a#b.com'; // pls change
$name = 'ab';// pls change
$data = new \stdClass();
$data->name = $name;
$data->sales_representative_name = \App\User::find(Auth::user()->id)->name;
$data->sales_representative_phone = \App\User::find(Auth::user()->id)->phones->first()->number;
$data->items = $order_items;
$data->address = $address;
$data->net = $net;
$data->payment = $payment;
$data->balance = $balance;
Mail::to($email)->send(new \App\Mail\OrderInfoMail($data));
I don't think the issue is your code. I think it is related to you sending practices. A solution is to use a service that is designed to send emails like SparkPost (full disclosure I work for SparkPost). There are many others. These services can help you make sure you are following email best practices.
You can make this work without an email service but at the very least you should verify you are following the best practices presented by MAAWG: https://www.m3aawg.org/published-documents
Related
I always have lots of problems with Mail::queue and this time the subject is not being applied properly.
This is my class:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class PlanExpiringOrExpired extends Mailable
{
use Queueable, SerializesModels;
private $payment = null;
public function __construct($payment)
{
$this->payment = $payment;
$this->subject($this->payment->subject);
\Log::debug("Subject: {$this->payment->subject}");
}
public function build()
{
$this->to($this->payment->email, $this->payment->name)
->view('mails/payment')
->with('payment', $this->payment);
return $this;
}
}
And I call it this way:
$payment = \App\Models\Payments::findOrFail($id);
$payment->subject = 'Your account has been canceled';
\Mail::queue(new \App\Mail\PlanExpiringOrExpired($payment));
The log saved correctly the following content:
[2023-02-12 11:00:04] local.DEBUG: Subject: Your account has been canceled
Yet the user received as subject: Plan Expiring or Expired (which is basically the class name).
Since I've done this change recently, do you think this might be a cache-related problem? If so, I'm using Supervisor to run queues, how do I clear the cache (through PHP) without messing up the production server?
I have used in the past something like this.
\Artisan::call('cache:clear');
But I'm not sure if this is correct, or if it has any implications for my production server.
Have you tried it this way to setup the proper subject?
private $payment = null;
public function __construct($payment)
{
$this->payment = $payment;
}
public function build()
{
$this->to($this->payment->email, $this->payment->name)
->subject($this->payment->subject)
->view('mails/payment')
->with('payment', $this->payment);
\Log::debug("Subject: {$this->payment->subject}");
return $this;
}
Move the subject set into build
iam doing like this in queue class, EmailContactForm is a mailable class.
public function handle()
{
$email = new EmailContactForm([
'locale' => $this->data['locale'],
'from_email' => $this->data['from_email'],
'name' => $this->data['name'],
'topic' => $this->data['topic'],
'subject' => $this->data['subject'],
'msg' => $this->data['msg']
]);
Mail::to($this->data['to_email'])
->bcc(config('app.mail_from_address'))
->send($email);
}
Solved.
It was indeed a cache problem, it is also necessary to restart the queue. My solution was to create a private endpoint like /superadmin/clear-cache and use it whenever I need.
Route::get('/superadmin/clear-cache', function()
{
\Artisan::call('cache:clear');
\Artisan::call('queue:restart');
});
I am working on a module to send chats messages to user email (aka email transcript) using laravel 5.6.
I need to save all the chat messages to a txt file and send that file as attachment to user's email address.
I do not want to save the txt file to my server as many people would be using that application and it will increase the storage usage of the server i.e I need to generate the txt file in memory.
I am able to populate the chats in plain email without attachment but this is not the solution if the chat messages increase, email would be too lengthy and seems not professional to me.
This I have tried so far:
EmailTranscriptController.php
<?php
namespace App\Http\Controllers\Home;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Trade;
use App\Models\ChatMessage;
use Auth;
use Illuminate\Support\Facades\Mail;
use App\Mail\EmailTradeChatMessages;
use Validator;
class EmailTranscriptController extends Controller
{
public function emailTradeTranscript($tradeId)
{
$userId = Auth::id();
$userEmail = Auth::user()->email;
$trade = Trade::findClosedTradeByIdByUserId($tradeId, $userId);
if (is_null($trade)) {
return response()->api(false, 'Trade not available', null);
}
$tradeStartTime = $trade->created_at;
$tradeCloseTime = $trade->updated_at;
$tradeChats = ChatMessage::getAllChatByTradeId($tradeId);
Mail::to($userEmail)->queue(new EmailTradeChatMessages(
$tradeChats,
$tradeStartTime,
$tradeCloseTime
));
return response()->api(true, 'Email Sent Successfully', null);
}
}
EmailTradeChatMessages.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailTradeChatMessages extends Mailable
{
use Queueable, SerializesModels;
protected $chats;
protected $tradeStartTime;
protected $tradeCloseTime;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($chats, $tradeStartTime, $tradeCloseTime)
{
$this->chats = $chats;
$this->tradeStartTime = $tradeStartTime;
$this->tradeCloseTime = $tradeCloseTime;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->markdown('emails.trade_chat_transcript')->with([
'chats' => $this->chats,
'tradeStartTime' => $this->tradeStartTime,
'tradeCloseTime' => $this->tradeCloseTime,
]);
}
}
trade_chat_transcript.blade.php (dummy)
#component('mail::message')
#Trade Started at: {{$tradeStartTime}}
#php
$count=0;
#endphp
#foreach($chats as $chat)
{{++$count}}
#endforeach
#Trade Closed at: {{$tradeCloseTime}}
Thanks,<br>
{{ config('app.name') }}
#endcomponent
Kindly help me getting the solution, I would also like to get other approaches to solution,if any.
Update
I found the solution for not storing the file on server itself and attach it using attachData() method, as follows:
public function build()
{
$email= $this->markdown('emails.trade_chat_transcript')->with([
'tradeId' => $this->tradeId,
'filename' => $this->filename,
'tradeStartTime' => $this->tradeStartTime,
'tradeCloseTime' => $this->tradeCloseTime,
])
->attachData($this->message,$this->filename,[
'mime'=>'text/plain'
]);
return $email;
}
Now I need to set metadata of the file to be attached in email eg. Author etc.
You need to create the file on the server. That being said you can delete it directly after. There is a method for that:
return response()->download($pathToFile)->deleteFileAfterSend(true);
I just created a newsletter in Laravel, where people can register with their email-adress and then somebody can send a mail to those who are registered.
Then I save those registrations in a table, together with the relating id of the sender of the newsletter (in my case its a seller, and buyers register for a newsletter)....
This is the code I use then so send a newsletter:
public function sendNewsletter(Request $request){
$newsletterText = $request->newsletterText;
$seller = Auth::user();
$sellerID = $seller->id;
$newsletterMailAdresses = Newsletter::where('seller_id', $sellerID)->pluck('mailAdress');
Mail::bcc($newsletterMailAdresses)->send(new newsletterMail($newsletterText));
return "<p style='color:green;'>Newsletter erfolgreich versandt</p>";
}
Its called with an AJAX request, but shouldn't change anything...
What I need now is to give the users the possibility to unsubscribe from the newsletter as well, via a link in the mail.
This is the corresponding mail class:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class newsletterMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $newsletterText;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($newsletterText)
{
$this->newsletterText = $newsletterText;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$subject = "Newsletter ShoppingPortal";
return $this->view('emails.newsLetter')->subject($subject);
}
}
And that's the mail view:
Newsletter vom Shoppingportal: <br/><br/>
<div>
{!!$newsletterText!!}
</div>
<br/>
To unsubscribe from Newsletter, use this link: localhost/unsubscribe/....
And here's the thing missing, I need to create a link to unsubscribe, therefore I would need the sellerID in the view (what would be possible, could just pass it in like the newsletterText, but HOW can I pass in or get the current mail adress the email is sent to? As this mail goes to some recipents, its different in each instance of the view... How can I do this, so how can I get the mail adress in the view?
I got it,
just changed the sending of the mail to this:
foreach($newsletterMailAdresses as $mailAdress){
Mail::to($mailAdress)->send(new NewsletterMail($newsletterText, $mailAdress, $sellerID));
}
Now I have the current mail adress while sending.
I am using built in laravel auth functionality.Its working fine.I am trying to override following two functionality.
1.send forgot password email using mandrill.
2.send verification email while registering account.
Can any one help me to solve this issue
My aim is to use mandril instead of default email
I can see auth built in methods but i didnt got idea how i can override that
trait ResetsPasswords
{
use RedirectsUsers;
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* #param \Illuminate\Http\Request $request
* #param string|null $token
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request, $token = null)
{
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Reset the given user's password.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function reset(Request $request)
{
$this->validate($request, $this->rules(), $this->validationErrorMessages());
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($response)
: $this->sendResetFailedResponse($request, $response);
}
As answered by Mahfuzal, Laravel comes with a bunch of mail drivers out of the box. So just update your .env file to use the right driver.
As for sending a verification email when creating an account, you just need to override the postRegister() function inside the Auth/AuthController like so:
public function postRegister(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
$confirmation_code = str_random(30);
$newUser = new User;
$newUser->username = $request->username;
$newUser->email = $request->email;
$newUser->password = bcrypt($request->password);
$newUser->confirmation_code = $confirmation_code;
$newUser->save();
$data = array('confirmation_code' => $confirmation_code, 'username' => $request->username);
Mail::send('emails.verify', $data, function ($message) use ($newUser){
$message->to($newUser->email, $newUser->username);
$message->subject('Please verify your email address');
});
return redirect('/auth/login');
}
This will execute the above code when registering a user rather than what Laravel does default out of the box so just tweak it to your needs.
You then just need to create a function that will check the token and verify their account when they click the link. For that, I use something similar to what is explained here.
Laravel provides drivers for SMTP, Mailgun, Mandrill, Amazon SES,
PHP's mail function, and sendmail, allowing you to quickly get started
sending mail through a local or cloud based service of your choice.
Open your .env file and change following by your Mandrill credentials and then you're good to go.
MAIL_DRIVER=mandrill
MAIL_HOST=
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
You can create your own reset method in the controller that uses the trait to override the method in the trait.
My system sends a couple of important emails. What is the best way to unit test that?
I see you can put it in pretend mode and it goes in the log. Is there something to check that?
There are two options.
Option 1 - Mock the mail facade to test the mail is being sent. Something like this would work:
$mock = Mockery::mock('Swift_Mailer');
$this->app['mailer']->setSwiftMailer($mock);
$mock->shouldReceive('send')->once()
->andReturnUsing(function($msg) {
$this->assertEquals('My subject', $msg->getSubject());
$this->assertEquals('foo#bar.com', $msg->getTo());
$this->assertContains('Some string', $msg->getBody());
});
Option 2 is much easier - it is to test the actual SMTP using MailCatcher.me. Basically you can send SMTP emails, and 'test' the email that is actually sent. Laracasts has a great lesson on how to use it as part of your Laravel testing here.
"Option 1" from "#The Shift Exchange" is not working in Laravel 5.1, so here is modified version using Proxied Partial Mock:
$mock = \Mockery::mock($this->app['mailer']->getSwiftMailer());
$this->app['mailer']->setSwiftMailer($mock);
$mock
->shouldReceive('send')
->withArgs([\Mockery::on(function($message)
{
$this->assertEquals('My subject', $message->getSubject());
$this->assertSame(['foo#bar.com' => null], $message->getTo());
$this->assertContains('Some string', $message->getBody());
return true;
}), \Mockery::any()])
->once();
For Laravel 5.4 check Mail::fake():
https://laravel.com/docs/5.4/mocking#mail-fake
If you just don't want the e-mails be really send, you can turn off them using the "Mail::pretend(true)"
class TestCase extends Illuminate\Foundation\Testing\TestCase {
private function prepareForTests() {
// e-mail will look like will be send but it is just pretending
Mail::pretend(true);
// if you want to test the routes
Route::enableFilters();
}
}
class MyTest extends TestCase {
public function testEmail() {
// be happy
}
}
If any one is using docker as there development environment I end up solving this by:
Setup
.env
...
MAIL_FROM = noreply#example.com
MAIL_DRIVER = smtp
MAIL_HOST = mail
EMAIL_PORT = 1025
MAIL_URL_PORT = 1080
MAIL_USERNAME = null
MAIL_PASSWORD = null
MAIL_ENCRYPTION = null
config/mail.php
# update ...
'port' => env('MAIL_PORT', 587),
# to ...
'port' => env('EMAIL_PORT', 587),
(I had a conflict with this environment variable for some reason)
Carrying on...
docker-compose.ymal
mail:
image: schickling/mailcatcher
ports:
- 1080:1080
app/Http/Controllers/SomeController.php
use App\Mail\SomeMail;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
class SomeController extends BaseController
{
...
public function getSomething(Request $request)
{
...
Mail::to('someone#example.com')->send(new SomeMail('Body of the email'));
...
}
app/Mail/SomeMail.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class SomeMail extends Mailable
{
use Queueable, SerializesModels;
public $body;
public function __construct($body = 'Default message')
{
$this->body = $body;
}
public function build()
{
return $this
->from(ENV('MAIL_FROM'))
->subject('Some Subject')
->view('mail.someMail');
}
}
resources/views/mail/SomeMail.blade.php
<h1>{{ $body }}</h1>
Testing
tests\Feature\EmailTest.php
use Tests\TestCase;
use Illuminate\Http\Request;
use App\Http\Controllers\SomeController;
class EmailTest extends TestCase
{
privete $someController;
private $requestMock;
public function setUp()
{
$this->someController = new SomeController();
$this->requestMock = \Mockery::mock(Request::class);
}
public function testEmailGetsSentSuccess()
{
$this->deleteAllEmailMessages();
$emails = app()->make('swift.transport')->driver()->messages();
$this->assertEmpty($emails);
$response = $this->someController->getSomething($this->requestMock);
$emails = app()->make('swift.transport')->driver()->messages();
$this->assertNotEmpty($emails);
$this->assertContains('Some Subject', $emails[0]->getSubject());
$this->assertEquals('someone#example.com', array_keys($emails[0]->getTo())[0]);
}
...
private function deleteAllEmailMessages()
{
$mailcatcher = new Client(['base_uri' => config('mailtester.url')]);
$mailcatcher->delete('/messages');
}
}
(This has been copied and edited from my own code so might not work first time)
(source: https://stackoverflow.com/a/52177526/563247)
I think that inspecting the log is not the good way to go.
You may want to take a look at how you can mock the Mail facade and check that it receives a call with some parameters.
if you are using Notifcations in laravel you can do that like below
Notification::fake();
$this->post(...);
$user = User::first();
Notification::assertSentTo([$user], VerifyEmail::class);
https://laravel.com/docs/7.x/mocking#notification-fake
If you want to test everything around the email, use
Mail::fake()
But if you want to test your Illuminate\Mail\Mailable and the blade, then follow this example. Say, you want to test a Reminder email about some payment, where the email text should have product called 'valorant' and some price in 'USD'.
public function test_PaymentReminder(): void
{
/* #var $payment SalePayment */
$payment = factory(SalePayment::class)->create();
auth()->logout();
$paymentReminder = new PaymentReminder($payment);
$html = $paymentReminder->render();
$this->assertTrue(strpos($html, 'valorant') !== false);
$this->assertTrue(strpos($html, 'USD') !== false);
}
The important part here is ->render() - that is how you make Illuminate\Mail\Mailable to run build() function and process the blade.
Another importan thing is auth()->logout(); - because normally emails being processed in a queue that run in a background environment. This environment has no user and has no request with no URL and no IP...
So you must be sure that you are rendering the email in your unit test in a similar environment as in production.