Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] Add ability to dynamically build mailers on-demand using Mail::build #53411

Merged
merged 3 commits into from
Nov 5, 2024

Conversation

stevebauman
Copy link
Contributor

Description

This PR adds the ability to build on-demand mailers using Mail::build($config).

This allows developers to create mailers based on a given configuration instead of being hard-coded in the config files.

This is really useful for circumstances where you may have mail configurations that are stored in your database, or another repository.

We currently have a way to build on-demand Storage disks using Storage::build, so I kept the same API.

Usage

use Illuminate\Support\Facades\Mail;

$mailer = Mail::build([
    'transport' => 'smtp',
    'host' => '127.0.0.1',
    'port' => 587,
    'encryption' => 'tls',
    'username' => 'usr',
    'password' => 'pwd',
    'timeout' => 5,
]);

$mailer->send($mailable);

Let me know your thoughts! No hard feelings on closure. Thanks so much for your time ❤️

$mailer = new Mailer(
$name,
$config['name'] ?? 'ondemand',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use ondemand as the driver name in the FilesystemManager:

return $this->resolve('ondemand', is_array($config) ? $config : [

@MrMooky
Copy link

MrMooky commented Nov 13, 2024

@stevebauman This does not seem to work when using ->queue() or ShouldQueue in the Mailable. It throws Mailer [ondemand] is not defined then.

@stevebauman
Copy link
Contributor Author

stevebauman commented Nov 13, 2024

Will check this, thanks @MrMooky!

@stevebauman
Copy link
Contributor Author

stevebauman commented Nov 13, 2024

@MrMooky Actually this is intended. You can't queue a mail to be sent with an on-demand mailer. The mailer wouldn't be able to be dynamically built inside of the queued job meant to send the mailable (it wouldn't have the config to do so).

The on-demand mailer should be used to send mail immediately, not place on a queue. If you need to queue the sending of the mail, place the creation of the mailer inside of a queued job:

namespace App\Jobs;

use App\Models\Mailbox;
use Illuminate\Support\Facades\Mail;
use Illuminate\Contracts\Mail\Mailable;

class SendMailboxEmail implements ShouldQueue
{
    public function __construct(
        public Mailbox $mailbox,
        public Mailable $email,
    ) {}

    public function handle(): void
    {
        Mail::build([
            'transport' => 'smtp',
            'host' => $this->mailbox->host,
            'port' => $this->mailbox->port,
            'encryption' => $this->mailbox->encryption,
            'username' => $this->mailbox->username,
            'password' => $this->mailbox->password,
            'timeout' => 5,
        ])->send($this->email);
    }
}

@MrMooky
Copy link

MrMooky commented Nov 14, 2024

@stevebauman Alright, I will give it a try. Thanks for getting back on this. :)

@MrMooky
Copy link

MrMooky commented Nov 14, 2024

@stevebauman Still getting Mailer [ondemand] is not defined with this method. Thanks anyway.

@stevebauman
Copy link
Contributor Author

@MrMooky I'm not able to reproduce that. Can you share reproduction steps? Make sure your mailable doesn't have the ShouldQueue interface on it.

I've tested this locally and it works:

use App\Models\Mailbox;
use Illuminate\Mail\Mailable;

$mailbox = Mailbox::first();

$email = (new Mailable)
    ->from($mailbox->email, $mailbox->name)
    ->to($request->to)
    ->cc($request->cc)
    ->bcc($request->bcc)
    ->html($request->body)
    ->subject($request->subject);

Mail::build($mailbox->config)->send($email);

@MrMooky
Copy link

MrMooky commented Nov 14, 2024

ShouldQueue in the mailable was the issue. Thanks, it's working now. :)

@stevebauman
Copy link
Contributor Author

Awesome @MrMooky! Glad you got it resolved 🎉

@stevebauman
Copy link
Contributor Author

@jishadp Post your mailable class

@jishadp
Copy link

jishadp commented Nov 18, 2024

@stevebauman Its a mistake from my end, Its working perfectly now...So many developers waiting this from India....
Thank you.

@stevebauman
Copy link
Contributor Author

Awesome, glad you got it sorted @jishadp!

@bmerheb
Copy link

bmerheb commented Nov 27, 2024

Mail::fake() is not working when we use Mail::build($config); It is actually trying to send real email from tests.

@stevebauman
Copy link
Contributor Author

@bmerheb Not a bug. Building a mailer in this way doesn't add it into the container. It's a runtime mailer stored in the function scope (not the application container), so you can't assert mail's being sent as you would traditionally.

You can instead use mockery:

Mail::shouldReceive('build->send')->once()->with(Mailable::class);

@MrMooky
Copy link

MrMooky commented Dec 10, 2024

This worked as expected since implementing it into my platform, now I'm getting the following error (haven't changed anything):

The "" scheme is not supported; supported schemes for mailer "smtp" are: "smtp", "smtps".

When calling this:

$mailer = Mail::build([
    'transport' => 'smtp',
    'host' => $this->smtpSettings->host,
    'port' => $this->smtpSettings->port,
    'encryption' => $this->smtpSettings->encryption,
    'username' => $this->smtpSettings->username,
    'password' => $this->smtpSettings->password,
    'timeout' => 5,
]);

@stevebauman
Copy link
Contributor Author

@MrMooky unrelated to this PR. Please see #53721 (comment) for resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants