How to Send Emails in PHP in 2023
Time to read: 10 minutes
Email! Yes. Email.
Despite what you might think, it is still one of the most used and trusted methods of communication. This might seem strange, given that it was invented back in the 60s and 70s. However, it's estimated that around 333 billion emails were sent — daily — in 2022. What's more, that figure's estimated to grow to over 376 billion by the end of 2025.
Why's this important — even interesting — you may be asking. After all, the cool kids are on Instagram, TikTok, and so many other social media channels. I won't deny that social media does get a lot of attention, and if you're looking for reach and engagement, perhaps they should be your focus.
But if you're looking for the greatest ROI (Return On Investment), a platform that isn't subject to changing algorithms, one that is more personal, and one that people significantly trust, then email is still the best choice!
So, in this article, you're going to learn three different ways to send emails with PHP; a combination of both classic and modern approaches.
Prerequisites
- PHP 8.0 or above
- Composer installed globally
- Sendmail (or equivalent), if you're running Linux or macOS, or an SMTP server, if you're running Microsoft Windows
- A free SendGrid account
- The mail command, if you're using Linux or macOS
- Your preferred editor or IDE (I recommend both Visual Studio Code and PhpStorm)
- Ideally, some prior experience with sending emails, Sendmail, SMTP, and Twig templates
Code overview
Each email will contain the same properties:
- A reply to address, a from address, and three recipients (one direct, one on CC, and one on BCC)
- The subject line "Sending emails with PHP is FUN!"
- A plain text and HTML body
- Two attachments, a text file and a PDF file
By doing this, none of the three approaches will have an unfair advantage over any other, and you'll be able to see what is required in each implementation.
Create the project directory structure
Start off by creating the project's directory structure and switch into it, by running the commands below.
If you're using Microsoft Windows, use the commands below instead.
Download the supporting files to send as attachments
Now that the project's directory structure's in place, download the two files which each email will send as attachments (text.txt and text.pdf) to the data directory.
Install the dependencies
Next, install the required dependencies; these are:
Dependency | Description |
---|---|
PHP Dotenv | PHP Dotenv populates PHP's $_SERVER and $_ENV superglobals from a configuration file. This encourages keeping sensitive configuration details out of code and version control. |
Symfony Mailer | This package is a feature-rich and very flexible way to create and send emails, having support for multipart messages, file attachments plus lots more. |
The Official Twilio SendGrid PHP API Library | This package simplifies sending emails with the Twilio SendGrid Web API with PHP. |
The Symfony Mailer SendGrid Transport | This package allows Symfony Mailer to send emails using SendGrid's API. |
The Symfony Twig Bundle | This package provides Twig support when sending emails using Symfony's Mailer component. |
Tukio | Tukio is an implementation of the PSR-14 Event Dispatcher specification. It will be used to know if an email was sent successfully or not when working with Symfony Mailer. |
To install them, run the command below.
If you're using Microsoft Windows, replace the backslash (\
) at the end of each line with a caret (^
).
Create a SendGrid API key
The next thing to do is to create and retrieve your SendGrid API key. To do that, log in to your SendGrid account and under Settings > API Keys click Create API Key. In the form that appears, enter a meaningful name for the key in the API Key Name field and click Create & View.
The SendGrid API key will now be visible. Copy and paste it into .env in place of the two <<SENDGRID_API_KEY>>
placeholders.
Set the required environment variables
You next need to set two environment variables so that the code can interact with SendGrid's API; these are
MAILER_DSN
: This contains all the information required for Symfony Mailer to instantiate a Transport class to send the email withSENDGRID_API_KEY
: This is required to make authenticated requests with SendGrid
To set them, create a new file named .env in the project's top-level directory and paste the configuration below.
Write the PHP code
Now, it's time to create the code to send emails with PHP.
Send an email with pure PHP
Let's start off by sending an email using PHP's mail() function. To do that, create a new file in the top-level directory of the project named php-mailer.php and paste the below code into it.
Near the top of the code, replace all of the email addresses in the $headers
array, with valid addresses that you have access to.
Now, let's step through the code. It starts off by defining a constant that holds the maximum line length of a MIME message, which is 79 chars. Then, it defines a function named addAttachment()
, which will add a MIME attachment to the email's message body.
The function checks if the file to be attached exists and is readable. If so, it then sets a MIME boundary pre-marker, the attachment's content type, content transfer encoding, and content disposition, before attaching the contents of the file to the message's body.
The contents are Base64-encoded so that all of the information will survive sending from sender to receiver, and then split into smaller chunks as necessitated by RFC 2045.
Then, it sets a series of headers, specifying the email's sender and reply-to addresses, along with any recipients on CC and BCC, adds the plain and HTML message body, and attaches the two files. Finally, it sends the email and prints out whether or not the message was sent successfully.
Send an email with the code
With the code written, send an email with it by running the command below.
Known issues with PHP's mail function
Before you move on to the second implementation, it's worth covering a few things about PHP's mail function.
While it is readily available in any PHP installation, it's not necessarily the best choice for sending emails either reliably or efficiently. Among other reasons, this is because:
- Mail servers may not accept emails if they're from servers that are not properly configured or if emails are not formatted correctly
- Generally speaking, you have to be familiar with a number of RFCs, such as:
- SMTP has different considerations to Sendmail implementations
- The
mail()
function is not suitable for large email volumes in a loop, as it opens and closes an SMTP socket for each email. - A lot of manual work and background knowledge is potentially required, which dedicated libraries handle for you. This includes knowing that
- Messages should be separated with a CRLF (
\r\n
). - Message line length should not be larger than 80 characters.
- When PHP is talking to an SMTP server directly (Windows only) if a full stop is found at the start of a line, it is removed.
- Retrieving error information when an email isn't successfully sent is different on Windows than on Linux/OSX.
- Messages should be separated with a CRLF (
- See the notes in the PHP manual for more information.
It may be quick and simple to use and available by default with any PHP installation, but there are often better choices, such as Symfony's Mailer component.
Send an email with Symfony's Mailer component
Now, let's work through an example of how to send an email with Symfony Mailer. To do that, create a new file in the project's top-level directory, named symfony-mailer.php. In the new file, paste the code below.
The code starts by loading the variables in .env as variables in PHP's $_SERVER
and $_ENV
superglobals. After that, it initialises two variables, $textAttachment
and $PDFAttachment
with the paths to two files to attach to the email.
Then, it initialises a new TemplatedEmail
object ($email
) which will store the email's properties; this object is required as the email will send a plain text and HTML body.
On the object, it:
- Sets the reply to, sender, and recipient addresses
- Sets the subject
- Attaches the text and PDF files
- Sets the text and HTML message bodies from two templates; which will be defined shortly
Then, a PSR-14 Event Dispatcher is initialised. This is so that a confirmation of the email being sent successfully or details as to why the email was unable to be sent, can be printed to the console. The dispatcher adds a listener to two events that Symfony Mailer fires:
- A FailedMessageEvent: This is fired when an email cannot be sent for some reason. If one is dispatched, the reason for the failure to send is printed to the terminal.
- A SentMessageEvent: This is fired when an email was successfully sent.
After that, a new Transport
object ($transport
) is initialised from the MAILER_DSN
environment variable that was set in .env. The DSN instructs the code that it will use the underlying SendGrid Transport
, and provides your SendGrid API key so that authenticated requests can be made successfully to SendGrid's API.
A Mailer
object ($mailer
), which handles sending the message with $transport
is then initialised.
Create the HTML body template
The plain text and HTML message bodies are generated from Twig templates, so let's create the templates, starting with the one for the HTML body.
Create a new file named html.body.twig in template/emails and in it, paste the code below.
It's not that special, just a small HTML file that includes a header and three paragraphs, the final one with a link.
Create the text body template
Next, create a new file named text.body.twig in template/emails and in it, paste the code below.
It's the plain text version of the previous HTML template.
Send an email with the code
Now that the code's written, send an email with it by running the command below.
Send an email with SendGrid's Library for PHP
Now, let's work through one final example of how to send an email, this time with SendGrid's Library for PHP. To do that, create a new file in the project's top-level directory, named sendgrid-mailer.php. In the new file, paste the code below.
The code starts off by loading the variables in .env as environment variables in PHP's $_SERVER
and $_ENV
superglobals. Then, it initialises two variables, one to hold the message's HTML body ($htmlBody
) and the other to hold the plain text body ($plainBody
).
Following that, it initialises a Mail
object ($email
) which stores the email's properties. On it, as with the Symfony Mailer implementation, it stores the sender and recipients email addresses, the reply to address, and subject. It also sets the message's plain text and HTML body, before adding the two attachments ($textAttachment
, $PDFAttachment
).
After that, it finishes up by initialising a new SendGrid object ($sendgrid
) with your SendGrid API key and attempts to send the email. If the response from SendGrid was a success, then the status code, and response's headers and body are printed to the terminal. If the email failed to send, the reason why is printed to the terminal.
Send an email with the code
Now that the code's written, send an email with it by running the command below.
All being well, you should see output, similar to the following printed to the terminal.
From the output, you can see that the status code is 202, indicating that SendGrid accepted the email and will send it at some point in the future.
How to debug email-sending problems
While it's great when emails arrive as expected. For quite a large number of reasons, such as anti-spam filters, SMTP servers being blacklisted, and SPF validation failure, this doesn't always happen.
So before you finish up this tutorial, let's look at a series of ways to help you debug emails that failed to arrive.
Use the mail command
When sending mail with a combination of PHP's mail() function and a local Sendmail daemon or SMTP server, a quick way to find out why an email failed to send is the mail command.
From the terminal, type mail
and press Enter. You will see output similar to the example below:
The subject of the two messages ("Undelivered Mail Returned to Sender") gives a broad indication of what went wrong. To look at one of the messages, press the number that precedes it, e.g., 1
or 2
. You'll then see output similar to the example below.
The message shows the specific reason why the email was not sent, about halfway down:
> The IP address sending this message does not have a 550-5.7.25 PTR record setup, or the corresponding forward DNS entry does not 550-5.7.25 point to the sending IP. As a policy, Gmail does not accept messages 550-5.7.25 from IPs with missing PTR records.
Use the SendGrid Dashboard
If you're sending emails with SendGrid's API, log in to the SendGrid Dashboard. Then, in the left-hand side navigation menu, expand the Suppressions section. In that section, you can find four subsections (Bounces, Spam Reports, Blocks, and Invalid) that may help determine why an email failed to arrive.
Look at SendGrid's response body
If you're using SendGrid, you can use the response body to view email formatting or validation errors and, potentially, how to fix them.
To do that, in sendgrid-mailer.php, update the try/catch block, at the end of the file, to match the following.
Now, if you run the script again, and an email fails to send, then you'll see output similar to the following, printed to the terminal.
There, you can see the reason why the email wasn't sent in the message
element, and a link to documentation that should be able to help you resolve the issue in the help
element.
That's three ways to send emails with PHP in 2023
While there are many ways to send an email with PHP in 2023, some methods are more efficient and reliable than others. I hope you'll continue exploring the functionality of both Symfony Mailer and SendGrid's PHP API Library. They're two excellent, feature-rich packages.
Matthew Setter is a PHP Editor in the Twilio Voices team and a PHP, Go, and Rust developer. He’s also the author of Mezzio Essentials and Docker Essentials. When he's not writing PHP code, he's editing great PHP articles here at Twilio. You can find him at msetter@twilio.com, and on Twitter, and GitHub.
Honeywell ad image used in the main post image via https://www.globalnerdy.com/wordpress/wp-content/uploads/2009/04/homeywell-electronic-mail-ad.jpg.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.