SendGrid & All About Transports

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

In .env, we're using the null transport. In .env.local, we're overriding that to send to Mailtrap. This is great for development, but it's time for our app to grow up, get a job, and join the real world. It's time for our app to send real emails through a real email server.

To do that, I recommend using a cloud-based email service... and Symfony Mailer can send to any service that supports the SMTP protocol... which is all of them. We did this for Mailtrap using the {username}:{password}@{server}:{port} syntax.

But to make life even nicer, Mailer has special support for the most common email services, like SendGrid, Postmark, Mailgun, Amazon SES and a few others. Let's use SendGrid.

Before we even create an account on SendGrid, we can jump in and start configuring it. In .env.local, comment-out the Mailtrap MAILER_DSN and replace it with MAILER_DSN=smtp://sendgrid. In Symfony 4.4, the syntax changed to sendgrid://default.

#MAILER_DSN=smtp://USERNAME:PASSWORD@smtp.mailtrap.io:2525
MAILER_DSN=smtp://sendgrid
# Symfony 4.4+ syntax
#MAILER_DSN=sendgrid://default

All About Transports

So far, we've seen two transports - two ways of delivering emails: the smtp transport and the null transport. Symfony also has a sendgrid transport, as well as a mailgun transport amazonses transport and many others.

In Symfony 4.3, you choose which transport you want by saying smtp:// and then the name of one of those transports, like null or sendgrid. In Symfony 4.4 and higher, this is different. The syntax is the transport name, like null or sendgrid then :// and whatever other options that transport needs. The word default is a dummy placeholder that's used when you don't need to configure a "server", like for the null transport or for sendgrid, because that transport already knows internally what the address is to the SendGrid servers.

Anyways, whether you're in Symfony 4.3 with the old syntax or Symfony 4.4 with the new one, this is how you say: "I want to deliver emails via the SendGrid transport".

At this point, some of you might be screaming

Wait! That can't possibly be all the config we need to send emails!

And you're 1000% percent correct. This doesn't contain any SendGrid username, or API key. Heck, we haven't even created a SendGrid account yet! All true, all true. But let's... try it anyways. Because, Symfony is going to guide us through the process. How nice!

Let Symfony Guide You to Configure the Transport

Head over to the browser and refresh. Woh! An immediate error:

Unable to send emails via Sendgrid as the bridge is not installed.

This is another example of Symfony making it easy to do something... but without bloating our project with stuff we don't need. Now that we do want to use Sendgrid, it helps us install the required library. Copy the composer require line, spin over to your terminal and paste:

composer require symfony/sendgrid-mailer

Ooh, this package came with a recipe! Let's see what it did:

git status

In addition to the normal stuff, this also modified our .env file. Let's see how:

git diff .env

Cool! The recipe added a new section to the bottom! Back in our editor, let's see what's going on in .env:

53 lines .env
... lines 1 - 48
###> symfony/sendgrid-mailer ###
# SENDGRID_KEY=
# MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid
###

Yea... this makes sense. We know that mailer is configured via a MAILER_DSN environment variable... and so when we installed the SendGrid mailer package, its recipe added a suggestion of how that variable should look in order to work with SendGrid.

SendGrid Symfony 4.4 Config Format

Now, two important notes about this. First, when you install this package in Symfony 4.4, the config added by the recipe will look a bit different: it will add just one line:

MAILER_DSN=sendgrid://KEY@default

Like we just talked about, this is because Symfony 4.4 changed the config format: the "transport type" is now at the beginning. The KEY is a placeholder: we'll replace with a real API key in a few minutes. And the @default part just tells the SendGrid transport to send the message to whatever the actual SendGrid hostname is.... we don't need to worry about configuring that.

A Note about Environment Variables inside Environment Variables

Now, if you look at the config that Symfony 4.3 uses, you'll notice the second important thing: this defines two environment variables. Gasp! It defines SENDGRID_KEY and then MAILER_DSN. This... is just a config trick. See how the MAILER_DSN value contains $SENDGRID_KEY? It's using that variable: it's environment variables inside environment variables! With this setup, you could commit this MAILER_DSN value to .env and then only need to override SENDGRID_KEY in .env.local.

This idea - the idea of using environment variables inside environment variables totally works in Symfony 4.4. But to keep the config a bit simpler, in Symfony 4.4 - you won't see this two-variable system in the recipe. Instead, we'll configure the entire MAILER_DSN value. After all, it's a pretty short string.

Next... let's actually do that configuration! It's time to create a SendGrid account and start using it.

Leave a comment!

This tutorial is built on Symfony 4.3, but will work well with Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.6.0
        "knplabs/knp-time-bundle": "^1.8", // v1.9.1
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.1,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.1.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.4.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.3.4
        "symfony/console": "^4.0", // v4.3.4
        "symfony/flex": "^1.9", // v1.9.10
        "symfony/form": "^4.0", // v4.3.4
        "symfony/framework-bundle": "^4.0", // v4.3.4
        "symfony/mailer": "4.3.*", // v4.3.4
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.3.4
        "symfony/sendgrid-mailer": "4.3.*", // v4.3.4
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.3.4
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "^4.0", // v4.3.4
        "symfony/web-server-bundle": "^4.0", // v4.3.4
        "symfony/webpack-encore-bundle": "^1.4", // v1.6.2
        "symfony/yaml": "^4.0", // v4.3.4
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.2.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "4.3.*", // v4.3.5
        "symfony/debug-bundle": "^3.3|^4.0", // v4.3.4
        "symfony/dotenv": "^4.0", // v4.3.4
        "symfony/maker-bundle": "^1.0", // v1.13.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.4
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.3.4
    }
}