Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Production Settings with SendGrid

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

If we're going to send emails with SendGrid... we... probably need an account! Head to sendgrid.com and click to register. I'll create a shiny new symfonycasts username, a thought-provoking password, my email and I am hopefully not a robot... and if I am... I'm at least a self-aware robot. Does that count? And... create account! Oh man! Registration step 2! Let's fill these out and... done!

SendGrid just sent us an email to verify my account. I've already got my inbox open and ready. There it is! I'll click to confirm my email and... we're good!

Creating the SendGrid API Key

Back on the SendGrid "guide" page, on a high-level, we need some sort of API key or username & password that we can use to send through our new account. Click "Start" and then "Choose" the SMTP Relay option.

Yea, I know, I know: SendGrid says that the Web API method is recommended. Most Cloud providers give you these two options: send through the traditional SMTP relay or use some custom API endpoints that they expose. They recommend the API way because, if you're creating all of your emails by hand, it's probably easier: just POST your subject, to, from, body, etc to an API endpoint and it takes care of creating the email behind-the-scenes. The API probably also has a few extra, SendGrid-specific features if you need to do something really custom.

But because Mailer - and really the Mime component - are handling all of the complexity of creating the email for us, it's much easier to use the SMTP relay.

Finally, it's time to create an API key that will authenticate us over SMTP. Give the key a name - just so you can recognize what it's for 1 year from now when we've completely forgotten. And hit "Create Key".

Check out our beautiful new SendGrid API key. Hmm, actually, down here, it's called a "Password". In reality, this is a SendGrid API key - you could use it to send emails through their RESTful API. But because SMTP authentication works via a username and password, SendGrid tells us to use apikey as the username and this as the password. It also tells us exactly what server and port to use. This is everything we need. Copy the password.

Configuring the SMTP Way vs the SendGrid Transport Way

In .env.local, we could use all that info to fill in the normal smtp://username:password@server:port format. That would totally work.

Or, we could use the SendGrid transport to make life easier: just smtp:// - the long API key - then @sendgrid.


The sendgrid transport is just a small wrapper around the SMTP transport to make life easier: because it knows that the username is always apikey... and that the server is always smtp.sendgrid.net, we don't need to fill those in.

In Symfony 4.4, the new syntax will look like this:


By the way, the SendGrid transport can use SMTP behind the scenes or make API requests to the SendGrid API. In fact, most transports are like this. Symfony chooses the best one by default - usually smtp - but you could force it to use the API by saying sendgrid+api://.

Sending an Email!


SendGrid now requires that you "authenticate" your from address before you can send any emails. We'll talk more about "sender authentication" in the next chapter, but to send your first email, you will need to do a few extra steps:

1) Follow https://sendgrid.com/docs/ui/sending-email/sender-verification/ to verify a real email address. For development, you can use your personal email.

2) In src/Service/Mailer.php, update the setFrom() line to use the email you just configured, instead of alienmailcarrier@example.com.

Ok team - let's try this! Back in the browser, tell SendGrid that we have updated our settings and click "Next".

At this point, unless we've made a mistake, it should work: SendGrid is waiting for us to try it. So... let's do that! Back on our site, hit enter on the registration page. This time, because we're going to send a real email - yay! - I'll register with a real address: ryan@symfonycasts.com. Type in a fun password, agree to terms and... go!

No errors!? Ho, ho! Because it probably worked. Tell SendGrid to "Verify Integration" - that makes it look for the email we just sent.

Our Message is Spammy

While we're waiting... ah! I see a new message in my inbox! And it looks perfect. If you don't see anything, double-check your spam folder. Because... the email we sent is actually super spammy. Why? See how we're sending from alienmailer@example.com? Do we own the example.com domain? No! And even if we did, we have not proven that our SendGrid account is allowed to send emails on behalf of that domain. This is the biggest mistake you can make when sending emails and we'll talk more about how to fix it in a few minutes.

But first, back on SendGrid... hmm. It didn't see my email? It definitely sent. Hit to verify again - sometimes this works quickly... but I've also had to hit this button 3-4 times before. So... keep trying.

Finally, it works. Next, our great new email system... will probably result in pretty much every email we send going straight to Spam. Wah, wah. We need to prove that we are allowed to send from whatever domain our "from" address is set to. Let's tackle "Sender authentication".

Leave a comment!

Login or Register to join the conversation
Juan-Etxenike Avatar
Juan-Etxenike Avatar Juan-Etxenike | posted 9 months ago | edited

Hello after making my code following this tutorial. I find that even if the profiler confirms that my mails get directed towards an external email account (on gmail) I still get them sent to the email that I have set from which is also the one verified at sendgrid. I cannot figure out why the email message does not get sent correctly to the outwards email address.

$email = (new TemplatedEmail())

    ->from('webmaster@xxxxxxx.com') //verified address
            'emailAddress' => $emailAddress,
            'url' => $url

Is there any limitation for sendgrid users of free accounts for sending emails and thus all get sent to the owner email address of the sendgrid?

Juan-Etxenike Avatar

Should I have paid attention to the following lesson I would then have found out that I had previously programmed the system to send all the mails to my account as had been done here, so please ignore my doubt, and thanks for such a great work.


Hey Juan,

Glad you figured out the problem yourself, well done! And thanks for sharing the exact reason with others, it might be helpful.


Miky Avatar

In symfony v6+ i am fighting with this problem https://github.com/symfony/...

Mail goes to the queue instead of being sent. By command bin/console messenger:consume i resolved issue with dev enviroment. How set up it that will work for production environment if i am using webhosting on which i deploy my symfony by symfony/apache-pack. Sorry i didn't read or worked with symfony messenger before.

MolloKhan Avatar MolloKhan | SFCASTS | Miky | posted 1 year ago | edited

Hey Miky

The easiest thing to do is to just don't route the SendEmailMessage in your messenger.yaml config file. Or, you can still route it but use the sync transport instead of the async

I hope it helps. Cheers!

Abel A. Avatar
Abel A. Avatar Abel A. | posted 3 years ago

Hello Guys,
have anyone try to use a transnational template with sendgrid using the smtp api? according to the sendgrid doc you have to send the template id ("template_id": "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f") on the X-SMTPAPI header but i don't see a way to send that parameter, would you please help??


Hey Abel,

Personally, I've never done this before, but I believe you can set additional email headers as:

$email->getHeaders()->addTextHeader('X-SMTPAPI', '%your heade value here%');

Just follow their docs to know which value exactly you need to set. Does it work for you?

I hope this helps!


Christina V. Avatar
Christina V. Avatar Christina V. | posted 3 years ago | edited

Hi folks !

Is anybody here has some experience with Mandrill API to share with ?

<b>Here is my context:</b>

Symfony 4.4
Symfony Mailer
Symfony mailchimp mailer
Symfony Messenger

I already tried to send email, in local with MailCatcher and everything works fine so I can asume my code is correct.
But with Mandrill... I can't see emails in their "outbound".

In my .env.local:

<br />##> symfony/mailchimp-mailer ###<br />MAILER_DSN=mandrill+api://my_key@default<br />

<a href="https://symfony.com/doc/current/components/mailer.html#transport&quot;&gt;https://symfony.com/doc/current/components/mailer.html#transport&lt;/a&gt;

If someone know or see something wrong.. Thanks!


Hey Christina V.!

I don't have direct experience, unfortunately :/. Have you tried the +smtp or +https versions of Mandrill? I think, from Mailer's standpoint, they're slightly more preferred - but, it *shouldn't* matter. Especially if you use the https one, you could add some debugging to this class in case it fails - https://github.com/symfony/... - and you should also see some logging I think.

I hope this helps!


Kiuega Avatar

Hey, it's really weird. Since April 6, 2020 we have to authenticate the sender. Thing I did. But when I try to send an email, I always come across this error:

Expected response code "250" but got code "550", with message "550 The from address does not match a verified Sender Identity. Mail cannot be sent until this error is resolved. Visit https://sendgrid.com/docs/f... to see the Sender Identity requirements".

Even when a sender's email address is validated. I tried at least 10 times recreating an API Key, but nothing, I still come across this error

Kiuega Avatar
Kiuega Avatar Kiuega | Kiuega | posted 3 years ago | edited

Hello Kiuega and @weaverryan !

Yes, that was what we had to do in the end! Thanks for the update !


Hey Kiuega,

We're sorry about that! Hm, did you verify your email address successfully? You should see "Sender Verified" message on success, see this guide: https://sendgrid.com/docs/u... . 550 error means "mailbox unavailable", from their docs I see the next possible reason behind of it:

The user’s mailbox was unavailable. Usually because it could not be found, or because of incoming policy reasons. Remove these address from your list - it is likely a fake, or it was mistyped

Or see more detailed explanation here about how to fix it: https://sendgrid.com/docs/f...

I hope this helps! Let me know if it does not.



Hi victor!

I just tested this by creating a new account. You're both correct that you DO now need to "verify your sender" before sending any emails - I got the exact same error before doing that. We'll add a note to the tutorial.

However, once I did "Single Sender Verification" - https://sendgrid.com/docs/ui/sending-email/sender-verification/ - and then updated the setFrom() calls in Mailer.php to use the email address i just used, everything worked fine. I did typo my email about 3 times... but once I made it match my "verified" address exactly, it worked just fine.



I'm looking for docs about how manage email on symfonyCloud via the "SendGrid sub-account" auto provisioned. You know the default adress ?


Hey Stephane!

Sorry for the slow reply :). I actually don't know much about this sub-account but I *think* that the idea is this: when you're getting started, using the sub-account is great because it's fast and easy. But as soon as you're going to production, you should create your own SendGrid account. That will allow you to do proper things like SPF setup on your domain so that the emails are deliverable.



Hey weaverryan
Thank for your answer. Finally I understand that there is no url for manage email on SymfonyCloud. But I succeeded to send email via messenger and notifier.

Cat in space

"Houston: no signs of life"
Start the conversation!

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.17.6
        "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