{"id":370,"date":"2026-09-10T11:41:16","date_gmt":"2026-09-10T17:11:16","guid":{"rendered":"https:\/\/photonconsole.com\/blog\/?p=370"},"modified":"2026-09-10T11:41:17","modified_gmt":"2026-09-10T17:11:17","slug":"laravel-mail-configuration-for-production-delivery","status":"publish","type":"post","link":"https:\/\/photonconsole.com\/blog\/laravel-mail-configuration-for-production-delivery\/","title":{"rendered":"Laravel Mail Configuration for Production Delivery"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Your Laravel app sends mail perfectly in local development. You push to production, and the emails stop arriving. No exception is thrown, no error appears in the logs, and <code>Mail::send()<\/code> returns without complaint. Users report that password resets never arrive and order confirmations never show up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In most cases the application code is correct. What changed is the mail configuration, the queue, or the SMTP host your app is now talking to. This guide covers the full production setup: the <code>.env<\/code> values that actually matter, mailable classes, queued sending, and the specific Laravel failures that let mail disappear without a trace.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Quick Answer: How Do You Configure Mail in Laravel?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Set the SMTP mailer in your <code>.env<\/code> file, then send through a mailable class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .env\nMAIL_MAILER=smtp\nMAIL_HOST=smtp.photonconsole.com\nMAIL_PORT=587\nMAIL_USERNAME=your_project_api_user\nMAIL_PASSWORD=your_secret_api_key\nMAIL_ENCRYPTION=tls\nMAIL_FROM_ADDRESS=\"noreply@yourdomain.com\"\nMAIL_FROM_NAME=\"${APP_NAME}\"<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>use Illuminate\\Support\\Facades\\Mail;\nuse App\\Mail\\OrderShipped;\n\nMail::to($user-&gt;email)-&gt;send(new OrderShipped($order));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Laravel reads these values through <code>config\/mail.php<\/code>. The application code rarely causes delivery failures. The host you authenticate against, and whether your domain is authorised to send, is what decides if the message reaches an inbox. An authenticated relay such as <a href=\"https:\/\/www.photonconsole.com\/\">PhotonConsole<\/a> on port 587 removes most of the failure modes described below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Laravel Actually Sends Mail<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Since Laravel 9, the framework uses <a href=\"https:\/\/symfony.com\/doc\/current\/mailer.html\" target=\"_blank\" rel=\"noopener\">Symfony Mailer<\/a> underneath (earlier versions used SwiftMailer). Your app builds a message through a mailable class, Laravel hands it to the configured transport, and the transport opens an SMTP connection to whatever host you named in <code>.env<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Laravel does not deliver the email itself. It is a client, not a mail server. That distinction explains most production failures: the Laravel side works exactly as written, but the transport it was pointed at cannot deliver.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Configuration key names have shifted between Laravel versions. Laravel 11 and later accept <code>MAIL_SCHEME<\/code> alongside <code>MAIL_ENCRYPTION<\/code>, and Laravel 9 changed mailable classes from the older <code>build()<\/code> method to <code>envelope()<\/code> and <code>content()<\/code>. Check the <a href=\"https:\/\/laravel.com\/docs\/mail\" target=\"_blank\" rel=\"noopener\">official Laravel mail documentation<\/a> for your specific version before copying configuration from an older tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Laravel Mail Fails in Production<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"512\" src=\"https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_01_32-PM-1-1024x512.png\" alt=\"\" class=\"wp-image-377\" style=\"aspect-ratio:1.7777777777777777;width:1200px;height:auto\" srcset=\"https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_01_32-PM-1-1024x512.png 1024w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_01_32-PM-1-300x150.png 300w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_01_32-PM-1-768x384.png 768w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_01_32-PM-1-1536x768.png 1536w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_01_32-PM-1.png 1774w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Four points where a Laravel application reports success but no email is delivered.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Most SMTP errors occur due to misconfiguration or authentication issues rather than faults in application code. These are the causes that account for the majority of Laravel delivery failures.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. MAIL_MAILER Is Still Set to log<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Recent Laravel releases ship with <code>MAIL_MAILER=log<\/code> as the default in <code>.env<\/code>. This writes the entire email into <code>storage\/logs\/laravel.log<\/code> instead of sending it. Everything appears to work \u2014 no error, no exception, and the mailable is rendered correctly. The message simply never leaves the server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the single most common reason a Laravel app &#8220;sends&#8221; mail that nobody receives.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Configuration Is Cached<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If <code>php artisan config:cache<\/code> has been run, Laravel reads from the cached config file and ignores <code>.env<\/code> entirely. You can correct every mail value in <code>.env<\/code>, deploy, and see no change at all, because the cached configuration still holds the old credentials.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. The Queue Worker Is Not Running<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If your mailable implements <code>ShouldQueue<\/code>, Laravel pushes the message onto a queue instead of sending it. With no worker process running, jobs accumulate in the <code>jobs<\/code> table and nothing is ever delivered. The application reports success because queueing the job succeeded.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. MAIL_FROM_ADDRESS Does Not Match an Authorised Domain<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If your from address is <code>noreply@yourdomain.com<\/code> but your domain has no SPF record covering the sending server, mailbox providers cannot verify the message and filter or reject it. This is covered fully in our guide to <a href=\"https:\/\/photonconsole.com\/blog\/spf-dkim-dmarc-explained-simply\/\">SPF, DKIM and DMARC<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Your Host Blocks Outbound SMTP<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">AWS, Google Cloud, DigitalOcean and Azure restrict outbound port 25 by default \u2014 AWS documents its <a href=\"https:\/\/repost.aws\/knowledge-center\/ec2-port-25-throttle\" target=\"_blank\" rel=\"noopener\">port 25 throttle removal process<\/a> \u2014 and many shared hosts throttle SMTP entirely. The connection stalls and eventually times out with no useful message. Our breakdown of <a href=\"https:\/\/photonconsole.com\/blog\/smtp-connection-timeout\/\">SMTP connection timeouts<\/a> covers the diagnosis.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Quick Fix<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Laravel Sends Locally but Not in Production<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Confirm <code>MAIL_MAILER=smtp<\/code>, not <code>log<\/code> or <code>array<\/code><\/li>\n\n\n\n<li>Run <code>php artisan config:clear<\/code> after any <code>.env<\/code> change<\/li>\n\n\n\n<li>Check whether your mailable implements <code>ShouldQueue<\/code>, and whether a worker is running<\/li>\n\n\n\n<li>Confirm <code>MAIL_PORT=587<\/code> with <code>MAIL_ENCRYPTION=tls<\/code>, or 465 with <code>ssl<\/code><\/li>\n\n\n\n<li>Look in <code>storage\/logs\/laravel.log<\/code> \u2014 if the full email body is there, the log driver is still active<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step-by-Step: Production Mail Setup<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Set the Correct Environment Values<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every mail value belongs in <code>.env<\/code>, never hardcoded in <code>config\/mail.php<\/code>. Keep <code>.env<\/code> out of version control.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MAIL_MAILER=smtp\nMAIL_HOST=smtp.photonconsole.com\nMAIL_PORT=587\nMAIL_USERNAME=your_project_api_user\nMAIL_PASSWORD=your_secret_api_key\nMAIL_ENCRYPTION=tls\nMAIL_FROM_ADDRESS=\"noreply@yourdomain.com\"\nMAIL_FROM_NAME=\"${APP_NAME}\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The from address matters more than it appears. It must be on a domain you have authenticated, not a Gmail or Yahoo address, or mailbox providers will treat the message as spoofed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Clear the Config Cache<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do this after every change to mail settings, on every environment.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan config:clear\n\n# In production, rebuild the cache afterwards\nphp artisan config:cache<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Common Mistake<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Editing <code>.env<\/code> on a production server without clearing the config cache. Laravel will keep using the cached values, so the new SMTP credentials are silently ignored and mail continues to fail in exactly the same way. If a configuration change appears to have no effect at all, this is almost always the reason.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Create a Mailable Class<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan make:mail OrderShipped --markdown=emails.orders.shipped<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>namespace App\\Mail;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Mail\\Mailable;\nuse Illuminate\\Mail\\Mailables\\Content;\nuse Illuminate\\Mail\\Mailables\\Envelope;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass OrderShipped extends Mailable\n{\n    use Queueable, SerializesModels;\n\n    public function __construct(public Order $order) {}\n\n    public function envelope(): Envelope\n    {\n        return new Envelope(\n            subject: 'Your order has shipped',\n        );\n    }\n\n    public function content(): Content\n    {\n        return new Content(\n            markdown: 'emails.orders.shipped',\n        );\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Verify the Connection Before Trusting It<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use Tinker to send a real message before wiring mail into your application flow.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan tinker\n\nMail::raw('Test message from Laravel', function ($m) {\n    $m-&gt;to('you@yourdomain.com')-&gt;subject('SMTP connection test');\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If this throws an exception, the problem is configuration or connectivity. If it returns cleanly but nothing arrives, the problem is delivery \u2014 authentication records or filtering, not Laravel.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 5: Queue Mail So Requests Stay Fast<\/h3>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img decoding=\"async\" width=\"1024\" height=\"512\" src=\"https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_05_01-PM-3-1024x512.png\" alt=\"\" class=\"wp-image-376\" style=\"aspect-ratio:1.7777777777777777;width:1200px;height:auto\" srcset=\"https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_05_01-PM-3-1024x512.png 1024w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_05_01-PM-3-300x150.png 300w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_05_01-PM-3-768x384.png 768w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_05_01-PM-3-1536x768.png 1536w, https:\/\/photonconsole.com\/blog\/wp-content\/uploads\/2026\/09\/ChatGPT-Image-Sep-10-2026-05_05_01-PM-3.png 1774w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Queueing returns the response immediately while a worker handles delivery separately.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Never make a user wait for an SMTP handshake during a web request. Implement <code>ShouldQueue<\/code> on the mailable and Laravel will push it to a background job automatically.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use Illuminate\\Contracts\\Queue\\ShouldQueue;\n\nclass OrderShipped extends Mailable implements ShouldQueue\n{\n    use Queueable, SerializesModels;\n    \/\/ ...\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Set a real queue driver in <code>.env<\/code> \u2014 the default <code>sync<\/code> driver runs the job immediately and defeats the purpose:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>QUEUE_CONNECTION=database\n# then:\nphp artisan queue:table\nphp artisan migrate\nphp artisan queue:work<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In production, run the worker under Supervisor or a systemd service so it restarts automatically. A queue worker that dies silently produces exactly the same symptom as broken SMTP: no errors, no email. Our guide to <a href=\"https:\/\/photonconsole.com\/blog\/transactional-email-queue-architecture-explained\/\">transactional email queue architecture<\/a> covers the wider pattern.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 6: Handle Failures Explicitly<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Laravel raises a <code>TransportException<\/code> when the SMTP conversation fails. Catch it and log the response rather than letting it disappear into a failed job.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use Illuminate\\Support\\Facades\\Log;\nuse Symfony\\Component\\Mailer\\Exception\\TransportExceptionInterface;\n\ntry {\n    Mail::to($user-&gt;email)-&gt;send(new OrderShipped($order));\n} catch (TransportExceptionInterface $e) {\n    Log::error('Mail transport failed', &#91;\n        'to'      =&gt; $user-&gt;email,\n        'message' =&gt; $e-&gt;getMessage(),\n    ]);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For queued mail, use the <code>failed()<\/code> method on the mailable, and monitor the <code>failed_jobs<\/code> table. For interpreting SMTP response codes inside these exceptions, see our reference on <a href=\"https:\/\/photonconsole.com\/blog\/smtp-response-codes-explained\/\">SMTP response codes<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Quick Fix<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Queued Mail Never Sends<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Confirm <code>QUEUE_CONNECTION<\/code> is not set to <code>sync<\/code> in production<\/li>\n\n\n\n<li>Check that <code>php artisan queue:work<\/code> is running under Supervisor<\/li>\n\n\n\n<li>Restart workers after every deploy with <code>php artisan queue:restart<\/code><\/li>\n\n\n\n<li>Inspect the <code>jobs<\/code> table \u2014 a growing row count means no worker is consuming<\/li>\n\n\n\n<li>Inspect <code>failed_jobs<\/code> for the actual exception message<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Port and Encryption Reference<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Mismatched port and encryption values cause connections that hang until they time out. Use one of these pairings \u2014 PhotonConsole accepts all three of the working combinations below.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>MAIL_PORT<\/th><th>MAIL_ENCRYPTION<\/th><th>When to use<\/th><\/tr><\/thead><tbody><tr><td>587<\/td><td>tls<\/td><td>Recommended default for most applications<\/td><\/tr><tr><td>465<\/td><td>ssl<\/td><td>When the provider requires implicit SSL<\/td><\/tr><tr><td>2525<\/td><td>tls<\/td><td>When the host blocks 587 and 465<\/td><\/tr><tr><td>25<\/td><td>null<\/td><td>Avoid \u2014 blocked by most hosting providers<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring Laravel With PhotonConsole<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Moving Laravel onto a dedicated relay is a change to <code>.env<\/code> and DNS. No application code changes, and mailable classes stay exactly as they are.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">DNS Records<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>TXT    @                    v=spf1 include:relay.photonconsole.com ~all\nCNAME  photon._domainkey    dkim.photonconsole.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DNS changes are not instant. SPF and DKIM records can take from a few minutes to 24-48 hours to propagate depending on your provider and TTL settings. Confirm propagation with a DNS lookup tool before assuming a record is misconfigured.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Environment Configuration<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>MAIL_MAILER=smtp\nMAIL_HOST=smtp.photonconsole.com\nMAIL_PORT=587          # 465 for implicit SSL, 2525 if 587 is blocked\nMAIL_USERNAME=your_project_api_user\nMAIL_PASSWORD=your_secret_api_key\nMAIL_ENCRYPTION=tls\nMAIL_FROM_ADDRESS=\"noreply@yourdomain.com\"\nMAIL_FROM_NAME=\"${APP_NAME}\"<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan config:clear\nphp artisan queue:restart<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Port 2525 exists specifically for hosting environments that block the standard SMTP ports, which resolves the AWS and DigitalOcean problem described earlier. Every account includes 5,000 free emails per month, enough to validate the full setup \u2014 DNS records, queued sending and failure handling \u2014 before any spend. Configuration details are on the <a href=\"https:\/\/www.photonconsole.com\/relay.php\">PhotonRelay page<\/a>, and <a href=\"https:\/\/www.photonconsole.com\/pricing.php\">pricing<\/a> has no monthly minimum.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you are still comparing options, our analysis of <a href=\"https:\/\/photonconsole.com\/blog\/free-smtp-servers\/\">free SMTP servers<\/a> covers where each one breaks down under production load.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Environment-Specific Notes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Local Development<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use a mail catcher such as Mailpit or Mailtrap rather than sending real messages from a development machine. Set <code>MAIL_MAILER=smtp<\/code> with the catcher&#8217;s host and port so the code path matches production, rather than using the <code>log<\/code> driver, which hides transport problems until deploy day.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Laravel Forge and Envoyer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Environment variables are managed in the panel, not by editing <code>.env<\/code> over SSH. Changes made directly on the server can be overwritten on the next deploy. Update the values in Forge and redeploy so the change persists.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Shared Hosting and cPanel<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Many shared hosts cap outbound mail per hour and route everything through their own server, which usually carries a poor sending reputation. Application email should go through an external relay rather than the host&#8217;s mail server.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Docker and Kubernetes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Containers have no local mail transfer agent, so an external relay is mandatory. Inject credentials through secrets rather than baking them into the image, and remember that <code>config:cache<\/code> run at image build time will freeze whatever values were present then.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pro Tips for Reliable Laravel Mail<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Always run <code>queue:restart<\/code> after deploying.<\/strong> Long-running workers keep old code in memory, including old mail configuration.<\/li>\n\n\n\n<li><strong>Set a monitored Reply-To address.<\/strong> Use the <code>replyTo<\/code> parameter in the envelope rather than leaving a noreply address as the only route back to you.<\/li>\n\n\n\n<li><strong>Use markdown mailables for consistency.<\/strong> They produce a plain-text alternative automatically, which improves spam filter scores.<\/li>\n\n\n\n<li><strong>Verify DNS after any change.<\/strong> <a href=\"https:\/\/mxtoolbox.com\/\" target=\"_blank\" rel=\"noopener\">MXToolbox<\/a> confirms SPF, DKIM and blocklist status in a single lookup.<\/li>\n\n\n\n<li><strong>Score a real send before launch.<\/strong> <a href=\"https:\/\/www.mail-tester.com\/\" target=\"_blank\" rel=\"noopener\">Mail Tester<\/a> flags authentication problems before customers encounter them.<\/li>\n\n\n\n<li><strong>Never log the mail password.<\/strong> Log the exception message and SMTP code, never the full config array.<\/li>\n\n\n\n<li><strong>Separate transactional and bulk queues.<\/strong> A marketing batch that gets throttled should never delay a password reset.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Related Issues You May Hit Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/photonconsole.com\/blog\/smtp-authentication-error\/\">SMTP authentication errors<\/a> when credentials are rejected despite being correct<\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/smtp-not-working\/\">SMTP not working<\/a> across the ten most common failure modes<\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/why-emails-go-to-spam-in-gmail\/\">Emails landing in Gmail spam<\/a> despite a successful send<\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/emails-sent-but-not-delivered\/\">Emails sent but not delivered<\/a> when the SMTP response says success<\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/smtp-configuration\/\">SMTP configuration<\/a> reference for host, port and encryption settings<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently Asked Questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Why does Laravel say mail was sent when nothing arrives?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Three usual causes: <code>MAIL_MAILER<\/code> is set to <code>log<\/code>, the config cache is stale, or the mailable is queued with no worker running. Check <code>storage\/logs\/laravel.log<\/code> first \u2014 if the full email body is written there, the log driver is still active.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need to run config:clear every time?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After any change to <code>.env<\/code>, yes. Laravel reads cached configuration in preference to <code>.env<\/code>, so an uncleared cache means your new values are ignored entirely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should mail be queued or sent synchronously?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Queue it in any web application. Synchronous sending makes the user wait for the full SMTP conversation, which adds seconds to the response and fails the whole request if the mail server is slow.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What is the difference between MAIL_ENCRYPTION and MAIL_SCHEME?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Older Laravel versions use <code>MAIL_ENCRYPTION<\/code> with values <code>tls<\/code> or <code>ssl<\/code>. Laravel 11 and later also accept <code>MAIL_SCHEME<\/code> with <code>smtp<\/code> or <code>smtps<\/code>. Check the documentation for your version rather than mixing conventions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I use Gmail SMTP for a Laravel application?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For local testing, yes. For production, no. Google publishes strict <a href=\"https:\/\/support.google.com\/a\/answer\/166852\" target=\"_blank\" rel=\"noopener\">Workspace sending limits<\/a>, throttles automated traffic, and can lock the account, which takes your authentication emails down with it. A dedicated relay such as PhotonConsole avoids that risk entirely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why do my emails go to spam even though Laravel sends them?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Delivery and inbox placement are separate. Laravel handing the message to a relay successfully says nothing about whether the receiving provider trusts your domain. Missing SPF or DKIM records are the usual cause.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I send email from a queued job in a different Laravel app?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The mail configuration is per application. Each app needs its own <code>.env<\/code> mail values, and each should authenticate with its own credentials so sending can be attributed and revoked independently.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Laravel&#8217;s mail layer is reliable. Almost every production failure traces back to one of four things: the log driver left active, a cached config that ignores your changes, a queue with no worker consuming it, or a sending domain with no authentication records.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fix those four and Laravel mail works predictably at any volume an application is likely to need. What remains is infrastructure rather than framework: whether your host permits outbound SMTP, and whether the receiving provider trusts the domain you send from.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your messages are being blocked by port restrictions, throttled by a mailbox provider, or filtered because your server has no sending reputation, no change to <code>config\/mail.php<\/code> will fix it. A dedicated <a href=\"https:\/\/www.photonconsole.com\/relay.php\">transactional email solution<\/a> handles authentication, routing and reputation, using the same <code>.env<\/code> configuration shown above. Developers working across stacks may also want our guides to <a href=\"https:\/\/photonconsole.com\/blog\/how-to-send-emails-in-node-js-with-nodemailer-a-production-setup-guide\/\">sending email in Node.js<\/a> and <a href=\"https:\/\/photonconsole.com\/blog\/sending-email-in-python-smtplib-vs-an-email-api-with-working-code\/\">sending email in Python<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Read More<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/photonconsole.com\/blog\/smtp-configuration\/\">SMTP Configuration: Complete Setup Reference<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/transactional-email-queue-architecture-explained\/\">Transactional Email Queue Architecture Explained<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/smtp-retry-logic-explained-for-transactional-email-systems\/\">SMTP Retry Logic for Transactional Email Systems<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/spf-dkim-dmarc-explained-simply\/\">SPF, DKIM and DMARC Explained Simply<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/how-to-send-emails-in-node-js-with-nodemailer-a-production-setup-guide\/\">Sending Email in Node.js with Nodemailer<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/photonconsole.com\/blog\/sending-email-in-python-smtplib-vs-an-email-api-with-working-code\/\">Sending Email in Python: smtplib vs an Email API<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.photonconsole.com\/relay.php\">PhotonRelay: SMTP Relay Service<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.photonconsole.com\/pricing.php\">PhotonConsole Pricing<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Your Laravel app sends mail perfectly in local development. You push to production, and the emails stop arriving. No exception is thrown, no error appears in the logs, and Mail::send() returns without complaint. Users report that password resets never arrive and order confirmations never show up. In most cases the application code is correct. What [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":371,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[312],"tags":[512,514,511,513,515],"class_list":["post-370","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-email-engineering-guide","tag-laravel-mail-config-production","tag-laravel-mail-not-sending","tag-laravel-queue-email","tag-laravel-smtp-configuration","tag-laravel-smtp-setup"],"_links":{"self":[{"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/posts\/370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/comments?post=370"}],"version-history":[{"count":1,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/posts\/370\/revisions"}],"predecessor-version":[{"id":378,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/posts\/370\/revisions\/378"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/media\/371"}],"wp:attachment":[{"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/media?parent=370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/categories?post=370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/photonconsole.com\/blog\/wp-json\/wp\/v2\/tags?post=370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}