Gửi mail tự động là một tính năng rất cần thiết khi xây dựng website. Hãy theo chân mình cùng nhau tìm hiểu cách gửi mail trong Laravel nhe!

Giới thiệu

Khi làm website, thì hầu hết đều phải đụng tới chức năng gửi mail nhất là các website TMĐT hoặc những website có chức năng quên mật khẩu và dùng email để khôi phục mật khẩu. Vậy làm sao để có thể gửi Mail tự động? Bây giờ mình sẽ bắt tay vào cấu hình và xây dựng chức năng gửi mail cho website của chính mình.

Cấu hình

File cấu hình nằm ở config/mail.php, ở đây mặc định đã được khai báo các giá trị cho SMTP, và một số driver khác mà Laravel hỗ trợ.

Mặc định SMTP đã được hỗ trợ sẵn, nên không cần cài đặt gì cả. Và mình cũng hướng dẫn rõ phần SMTP này.

Để sử dụng Driver gửi mail mà bạn mong muốn, thử update file .env như sao:
Thay đổi file .env như sau:

MAIL_MAILER=xxx

xxx: smtp / ses / mailgun / postmark.

Lưu ý: Ứng dụng phải được cài đặt thư viện Guzzle HTTP. Nếu chưa cài đặt thì thử như sau:

composer require guzzlehttp/guzzle

SMTP
Mặc định những biến môi trường đã được khai báo sẵn trong file .env

MAIL_MAILER=smtp
MAIL_HOST=smtp.googlemail.com
MAIL_PORT=465
MAIL_USERNAME=datxxx@gmail.com
MAIL_PASSWORD=xxxxxxxx
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=datxxx@gmail.com
MAIL_FROM_NAME="${APP_NAME}"
  • MAIL_DRIVER (phương thức gửi mail) là smtp.
  • MAIL_HOST (host) của Gmail là smtp.googlemail.com hoặc smtp.gmail.com.
  • MAIL_PORT (cổng kết nối đến Gmail) là 465 hoặc 587.
  • MAIL_USERNAME là chính gmail của bạn.
  • MAIL_PASSWORD là mật khẩu gmail của bạn.
  • MAIL_ENCRYPTION có thể đổi thành tls.
    Đổi 1 lần cả 3 thông tin trên nếu không gửi mail được nhá.

Khi sử dụng password bạn cần chú ý một vài điều:

Nếu Gmail của bạn bật bảo mật 2 lớp (Two-factor Authentication) thì bạn cần tạo ra mật khẩu ứng dụng 1 lần của gmail để có thể sử dụng được, bạn có nhập mật khẩu của gmail thì cũng không thể dùng được trong trường hợp này. Mình không khuyến khích các bạn bỏ lớp bảo mật nâng cao này đi. (Ở cuối bài mình sẽ hướng dẫn các bạn cách để lấy mật khẩu ứng dụng của gmail)

Mailgun Driver
Bạn xem trong file config/services.php xem và cập nhật trong file .env hoặc thay đổi trực tiếp trong file config:

'mailgun' => [
    'domain' => env('MAILGUN_DOMAIN'),
    'secret' => env('MAILGUN_SECRET'),
],

Postmark Driver
Để sử dụng Postmark cần cài Postmark’s SwiftMaile bằng composer:

composer require wildbit/swiftmailer-postmark

Tương tự Mailgun, bạn vào config/services.php và cập nhật thông tin:

'postmark' => [
    'token' => env('POSTMARK_TOKEN'),
],

SES Driver

composer require aws/aws-sdk-php

Config:

'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],

Failover Configuration
failover được coi là trường hợp dự phòng nếu như cấu hình mặc định bị lỗi trong quá trình gửi mail.

Tạo Mailables

Sử dụng lệnh command để tạo. Như mình có nói, những gì tạo được bằng lệnh thì mình khuyến khích dùng lệnh để tạo nha. 1 là nhanh, 2 là đầy đủ và đúng cú pháp.

php artisan make:mail OrderShipped

File app\Mail\OrderShipped.php được sinh ra và chúng ta sẽ soạn nội dung và các thông tin khi gửi cho người dùng từ đây:

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('Order Shipped')
                    ->view('view.name');
    }
}

Sau đó tạo file view để có thể gửi đi.

Tương tự các chức năng khác Laravel hỗ trợ, các bạn có thể inject bất cứ thứ gì các bạn cần trong hàm construct.
Và chúng ta có thể handle gì đó trước return trong hàm build. Trong hàm này, bạn có thể gọi các hàm khác như: fromsubjectview hay attach.
View mặc định tính từ thư mục views như phần website.

Gửi data qua view

Nếu các bạn khai báo thuộc tính là public thì các bạn dễ dàng gọi nó ra từ view:

trong file resources/views/emails/orders/shipped.blade.php có gọi trực tiếp nó ra:

<div>
    Price: {{ $order->price }}
</div>

Nếu khai báo private hoặc protected thì chúng ta sử dụng with để truyền dữ liệu qua view:

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The order instance.
     *
     * @var \App\Models\Order
     */
    protected $order;

    /**
     * Create a new message instance.
     *
     * @param  \App\Models\Order  $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('Order Shipped')
                    ->view('emails.orders.shipped')
                    ->with([
                        'orderName' => $this->order->name,
                        'orderPrice' => $this->order->price,
                    ]);
    }
}

hoặc đơn giản bạn truyền nguyên cái $order qua view luôn:

<div>
    Price: {{ $orderPrice }}
</div>

Attachments

Để đính kèm file khi gửi mail, sử dụng phương thức attach:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->view('emails.orders.shipped')
                ->attach('/path/to/file');
}

Có thể truyền thêm các option vào attach:

public function build()
{
    return $this->view('emails.orders.shipped')
                ->attach('/path/to/file', [
                    'as' => 'name.pdf',
                    'mime' => 'application/pdf',
                ]);
}

Nếu file nằm trong filesystem disks của bạn, bạn có thể sử dụng phương thức attachFromStorage:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
   return $this->view('emails.orders.shipped')
               ->attachFromStorage('/path/to/file');
}

hoặc attachFromStorageDisk khi sử dụng disk khác mặc định

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
   return $this->view('emails.orders.shipped')
               ->attachFromStorageDisk('s3', '/path/to/file');
}

Gửi Mail

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class OrderShipmentController extends Controller
{
    /**
     * Ship the given order.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $order = Order::findOrFail($request->order_id);

        // Ship the order...
        $mailable = new OrderShipped($order);
        $toEmail = $request->user()->email;
        Mail::to($toEmail)->send($mailable);
    }
}

Nếu sử dụng phương thức send để gửi mail, thì phải tốn khác nhiều thời gian để xử lý xong tác vụ này, và gây cảm giác khó chịu cho người dùng. Thay vì đó ta sử dụng phương thức queue để đưa mail vào hàng đợi và dùng queue để xử lý việc gửi mail:

public function store(Request $request)
    {
        $order = Order::findOrFail($request->order_id);

        // Ship the order...
        $mailable = new OrderShipped($order);
        $toEmail = $request->user()->email;
        Mail::to($toEmail)->queue($mailable);
    }

Mặc định khi gọi phương thức queue, jobs gửi mail sẽ sử dụng queue default. Bạn có thể thay đổi để tăng tốc tốc độ gửi mail tránh những jobs khác trong hàng đợi bằng cách khai báo queue trong class OrderShipped:

/**
     * Create a new message instance.
     *
     * @param  \App\Models\Order  $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
        $this->queue = 'email';
    }

và chạy queue:listen

php artisan queue:listen --queue=email

Bạn có thể tự do chỉ định người nhận “to”, “cc”, hay “bcc” bằng cách gọi các phương thức đó ra như sau:

Mail::to($toEmail)
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->send($mailable);

Tips

Trong một số website có những chức năng đặt biệt không những gửi mail mặc đinh, còn có cả gửi mail theo nhu cầu người dùng hay driver tùy thích của người dùng. Ta có thể config trực tiếp ngay hàm xử lý việc gửi mail này:
Một code mẫu mình đã dùng để test mail trước khi người dùng config mail vào hệ thống. Lưu ý là config bằng tay nhé và lưu vào database chứ không phải config vào file .env, ở đây mình chỉ dùng smtp và ses thôi:

public function checkConfigMail(Request $request)
  {
      switch ($request->mail_driver) {
          case 'smtp':
              config([
                  'mail.default' => 'smtp',
                  'mail.mailers.smtp.host' => $request->mail_host,
                  'mail.mailers.smtp.port' => $request->mail_port,
                  'mail.mailers.smtp.encryption' => $request->mail_encryption,
                  'mail.mailers.smtp.username' => $request->mail_username,
                  'mail.mailers.smtp.password' => $request->mail_password,
                  'mail.from.address' => $request->mail_address,
                  'mail.from.name' => $request->mail_name,
              ]);
              break;
          case 'ses':
              config([
                  'mail.default' => 'ses',
                  'mail.from.address' => $request->mail_address,
                  'mail.from.name' => $request->mail_name,
                  'services.ses.key' => $request->mail_key,
                  'services.ses.secret' => $request->mail_secret,
                  'services.ses.region' => $request->mail_region
              ]);
              break;
      }

      Mail::to($request->mail_test)->send( new CheckMailConfig());
      return response()->json([
          'success' => true
      ]);
  }

Và mình override config đó vào service providers như sau. Mở file app\Providers\AppServiceProvider.php, và cập nhật như sau:

Các bạn có thể lưu data trong database như nào tùy tích nhá, miễn sao các bạn gọi đống data do người dùng tự config ra là được. Ở đây mình dùng helper để lấy những giá trị của nó ra

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->bootMailConfig();
    }

    protected function bootMailConfig()
    {
        $config = $this->app['config'];

        $defaultConfigMail = $config->get('mail');

        $newConfigMail = [
            'default' => setting('mail_driver') ?? env('MAIL_DRIVER', 'smtp'),
            'mailers' => [
                'smtp' => [
                    'transport' => 'smtp',
                    'host' => setting('mail_host') ?? env('MAIL_HOST', 'smtp.mailgun.org'),
                    'port' => setting('mail_port') ?? env('MAIL_PORT', 587),
                    'encryption' => setting('mail_encryption') ?? env('MAIL_ENCRYPTION', 'tls'),
                    'username' => setting('mail_username') ?? env('MAIL_USERNAME'),
                    'password' => setting('mail_password') ?? env('MAIL_PASSWORD'),
                ],
                'ses' => [
                    'transport' => 'ses',
                ],
            ],
            'from' => [
                'address' => setting('mail_address') ?? env('MAIL_FROM_ADDRESS', 'noreply.newnet@gmail.com'),
                'name' => setting('mail_name') ?? env('MAIL_FROM_NAME', 'Newnet'),
            ],
        ];

        $this->app['config']->set('mail', array_merge($defaultConfigMail, $newConfigMail));

        $defaultConfigService = $config->get('services');

        $newConfigService = [
            'ses' => [
                'key' => setting('mail_key') ?? env('AWS_ACCESS_KEY_ID'),
                'secret' => setting('mail_secret') ?? env('AWS_SECRET_ACCESS_KEY'),
                'region' => setting('mail_region') ?? env('AWS_DEFAULT_REGION', 'us-east-1'),
            ],
        ];

        $this->app['config']->set('services', array_merge($defaultConfigService, $newConfigService));
    }
}

Hãy thử cách này nếu các bạn cần nhé.

Lấy mật khẩu ứng dụng của Gmail

Như ở trên mình đã nói, nếu bạn sử dụng bảo mật 2 lớp của Gmail thì cần phải có mật khẩu ứng dụng thì mới có thể sử dụng được chức năng gửi email này. Nên sau đây mình sẽ hướng dẫn các bạn lấy mật khẩu ứng dụng của gmail.

Đầu tiên, các bạn truy cập vào trang quản lí tài khoản của Google tại đây. Sau khi truy cập và đăng nhập bạn sẽ thấy màn hình sau:

Ở cột bên trái, các bạn chọn Bảo mật.

Tại đây, bạn có thể thấy phần Đăng nhập vào Google có phần Xác minh 2 bước đang bật, tức là tài khoản của bạn đang được bảo vệ với lớp bảo mật nâng cao. Nếu chưa bạn click vào Xác minh 2 bước và bật nó lên. Nó sẽ bắt bạn nhập mật khẩu và xác thực số điện thoại hay phương thức nào mà bạn lựa chọn.

Tiếp đến bạn chọn Mật khẩu ứng dụng, khi này Google sẽ yêu cầu bạn nhập lại mật khẩu một lần nữa. Sau khi nhập lại mật khẩu thì bạn sẽ thấy màn hình này.

Giờ bạn sẽ chọn Ứng dụng và Thiết bị, sau đó ấn Tạo.

Sau khi Tạo thì bạn sẽ nhận được một chuỗi gồm 16 kí tự, đó là mật khẩu ứng dụng của bạn. Bạn dùng mật khẩu này để dán vào phần MAIL_PASSWORD trong project trên.

Kết luận

Trên đây mình giới thiệu về cách gửi maii và config mail cho tính năng phát sinh. Nội dung phía trên hiện tại chỉ là kiến thức của mình, các bạn hãy thực hành nhiều để nó trở thành kiến thức của các bạn nhé! Hy vọng bài viết sẽ giúp ích cho các bạn trong quá trình học tập và làm việc.
Nếu có sai sót gì mong các bạn đóng góp ý kiến ở phần bình luận hoặc trao đổi trực tiếp qua Facebook hoặc Email mình có gắn phía cuối trang web.

Nguồn tham khảo:

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!