You’re developing a back-end in PHP (Laravel, Lumen, or just freewheeling it) and you want it to send out email when users use your “forgot password” functionality. Getting everything configured just right can be a headache and this article lists all the things you need to do. You could also reach for a third-party solution, but then you’d have to have your credit card handy.

Using a 3rd-party service?

If you don’t want to code the email sending yourself, you can reach for a third-party solution. Mailgun, Sendgrid and Mailchimp come to mind, but they all cost money. Mailgun has a free tier, but it’ll only work for sending out email to up to 5 known email addresses, and that’s not what you want - you want to send email out into the world (but only one email at a time). Even if you did pay for a third-party solution, it may have a free tier of up to, say, 1,000 emails per month, after which you start paying. God forbid that your API key falls into the wrong hands and someone sends out a million emails with it!

Using your own server

In my case, I have a shared-hosting server that runs PHP. Through CPanel, I am able to create email addresses, so I can go ahead and create info@myserver.org and use that to send email from.

Problem 1: Email sent manually from my server does not arrive

With so many spammers around, email has become something like the Wild West. Anyone can send out emails, and users are bombarded with spam. To fight some of this, email providers try to block emails where it can’t be determined where the email came from - in which case it could be a spoofed address.

Sending out a simple email manually from my CPanel email client to a GMail address will result in this (I’ll get an email back from CPanel or from Gmail):

550-5.7.26 This message does not pass authentication checks (SPF and DKIM both 
550-5.7.26 do not pass).
550-5.7.26 This mail is unauthenticated, which poses a security risk to the 
550-5.7.26 sender and Gmail users, and has been blocked. The sender must 
550-5.7.26 authenticate with at least one of SPF or DKIM. For this message, 
550-5.7.26 DKIM checks did not pass and SPF check for [example.com] 
550-5.7.26 did not pass with ip: [x.y.z.n]. The sender should visit 
550-5.7.26 https://support.google.com/mail/answer/81126#authentication for 
550 5.7.26 instructions on setting up authentication.

In other words, if your domain doesn’t have SPF and DKIM records, GMail (and likely others) will reject your mails straight out of the gate.

A solution is brought by CPanel itself: the Email deliverability option.

Accessing Email Deliverability on CPanel

If you have no DKIM or SPF records setup, then the Email Deliverabilty screen will report that there is a problem and offer to repair it:

Repaint DKIM problem

If your DNS is actually under CPanel’s control, you can CPanel create SPF and DKIM records automatically. If not, and like me, you have your domain at GoDaddy while you’re hosting somewhere else, it’s not going to be automatic. But never fear, CPanel can still help. If you choose the Manage option, CPanel will tell you that no DKIM record exists, and provide you with a suggested record:

This system does not control DNS for the “myserver.org” domain and the system did not find any authoritative nameservers for this domain. You can install the suggested “DKIM” record locally. However, this server is not the authoritative nameserver. If you install this record, this change will not be effective. Contact your domain registrar to verify this domain’s registration.

Suggested DKIM (TXT) record:
Name = default._domainkey.www.myserver.org.
Value = v=DKIM1; k=rsa; p=a-long-encrypted-value;
Suggested SPF (TXT) record:
Name = www.myserver.org.
Value = v=spf1 +mx +a +ip4:(ip number) ~all

You can actually take these values to GoDaddy and create the DNS records yourself. Note that both need to be TXT records. Create these and save them, wait a few minutes, and then manually send an email again. It should now arrive without issue into a GMail inbox.

Getting Laravel’s mailer to behave

With the TXT records configured, I found that while I could send emails manually through the email web client and receive them, emails from PHP would not be received.

It turns out that for Lumen, after configuring all that’s necessary for Illuminate/mail, no email would be sent. I also found that the email sending route returns rather quickly; ordinarily it would be processing a little while before returning.

I there checked my STMP settings using the GMass SMTP Test Tool which came in handy. I found that I was able to send email email through this and receive it in a GMail inbox, so my SMTP settings were correct. I also noted that sending the email took a few seconds, so it was suspicious that the Laravel code returned immediately.

Finding no way to get the Laravel code to tell me why it failed, I installed the venerable PHPMailer instead. Testing it out by pasting its sample code into my back-end implementation, it immediately worked.

Stepping away from Laravel’s SwiftMailer was made easier by using Laravel’s ENV variables in PHPMailer instead of using hardcoded values (these are the settings from the .env file):

$mail->Host       = env('MAIL_HOST');            // Set the SMTP server to send through
$mail->SMTPAuth   = true;                        // Enable SMTP authentication
$mail->Username   = env('MAIL_USERNAME');        // SMTP username
$mail->Password   = env('MAIL_PASSWORD');        // SMTP password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; // Enable implicit TLS encryption
$mail->Port       = env('MAIL_PORT', 2525);      // TCP port

Further, it’s still possible to use Laravel’s Blade templates by rendering them to string:

$mail->isHTML(true);
$mail->Subject = 'Database password reset';
$mail->Body    = view('reset', compact('user'))->render();
$mail->send();

I know I’ve had Laravel’s SwiftMailer work for me in the past, but PHPMailer does me the courtesy of letting me put a trycatch around the mail sending process so that I’ll know when it’s failed. Also, a full SMTP debug can be echoed to the output to see precisely what went wrong.