Xử lý thư điện tử với Laravel Mail

Thư điện tử là một trong những cách trao đổi thông tin, quảng bá nhanh trong bối cảnh Internet đang bùng nổ. Trong các hệ thống website, hệ thống gửi thông tin cho người dùng thông qua email là không thể thiếu.

Framework Laravel cũng rất chú trọng đến thư điện tử, với Laravel Mail bạn sẽ dễ dàng tạo ra những ứng dụng có khả năng gửi nhận email. Hệ thống API của Laravel Mail rất đơn giản, rõ ràng khi sử dụng thư viện của SwiftMailer, nó cho phép làm việc với mail server qua SMTP, với các dịch vụ email như Mailgun, SparkPost, Amazon SES, MailTrap…

Mailgun và SparkPost là các dịch vụ email có tốc độ nhanh hơn SMTP thông thường, tuy nhiên khi sử dụng các dịch vụ này cần cài đặt thêm một package Guzzle HTTP, chúng ta có thể cài đặt thông qua composer như sau:

composer require guzzlehttp/guzzle

Thiết lập mail driver

Khi sử dụng các mail driver cho các dịch vụ thư điện tử khác nhau, chúng ta cần khai báo trong config/mail.php.

    /*
    |--------------------------------------------------------------------------
    | Mail Driver
    |--------------------------------------------------------------------------
    |
    | Laravel supports both SMTP and PHP's "mail" function as drivers for the
    | sending of e-mail. You may specify which one you're using throughout
    | your application here. By default, Laravel is setup for SMTP mail.
    |
    | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses",
    |            "sparkpost", "log", "array"
    |
    */

    'driver' => env('MAIL_DRIVER', 'mailgun'),

driver hỗ trợ smtp, mailgun, ses, sparkpost… khi bạn chọn dịch vụ email nào thì cần thiết lập driver cho tương ứng. Sau đó, bạn cần thiết lập các thông số cho dịch vụ email trong config/service.php.

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

    'ses' => [
        'key' => env('SES_KEY'),
        'secret' => env('SES_SECRET'),
        'region' => 'us-east-1',
    ],

    'sparkpost' => [
        'secret' => env('SPARKPOST_SECRET'),
    ],

Tạo mới Mailables Class

Trong Laravel, các dạng email khác nhau được gửi đi đều thông qua Mailables Class, các class này đều được lưu trong thư mục app\Mail. Nếu bạn không thấy thư mục app\Mail cũng đừng lo lắng, các lệnh tạo mới class Mailables sẽ tự động tạo ra thư mục này. Để tạo mới một class Mailables chúng ta sử dụng lệnh artisan như sau:

php artisan make:mail RegisteredUser

Trong các class Mailables có phương thức build() sử dụng để thiết lập email cần gửi đi, trong phương thức build() này bạn có thể sử dụng rất nhiều các phương thức khác liên quan đến email như from, attach, subject…

Thiết lập người gửi email

Trong một email, thông tin người gửi là quan trọng nhất để nhận biết email này đến từ đâu. Thiết lập người gửi email trong Laravel Mail có hai cách: cách thiết lập cho từng email và cách thiết lập cho toàn bộ các email. Để thiết lập người gửi cho từng email chúng ta sử dụng phương thức from().

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->from('allaravel.com@gmail.com')
                ->view('emails.user.registered');
}

Chúng ta cũng có thể thiết lập người gửi cho toàn bộ các email bằng cách cấu hình file config/mail.php:

    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'allaravel.com@gmail.com'),
        'name' => env('MAIL_FROM_NAME', 'All Laravel Admin'),
    ],

Hoặc thậm chí trong file thiết lập các biến môi trường .env ở thư mục gốc của dự án:

MAIL_FROM_ADDRESS=allaravel.com@gmail.com
MAIL_FROM_NAME=All Laravel Admin

Xây dựng giao diện và nội dung của email gửi bằng Laravel Mail

Trong phương thức build() của class Mailables chúng ta có thể sử dụng view() để chỉ định một Laravel Blade template làm mẫu cho email và có thể truyền các đối tượng vào template này.

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    $user = User::find($registered_user_id)
    return $this->view('emails.user.registered')->with('user', $user);
}

Để truyền dữ liệu vào class Mailables chúng ta sử dụng các thuộc tích public, nó sẽ tự động được gán giá trị khi tạo một instance của class Mailables.

<?php

namespace App\Mail;

use App\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class UserRegistered extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The order instance.
     *
     * @var Order
     */
    public $user;

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

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.user.registered')->with('user', $this->user);
    }
}

Những chỗ nào cần gửi mail ta chỉ đơn giản thực hiện tạo một instance của class Mailables như sau:

$user = User::find($user_id);
$email = new UserRegistered($user);
Mail::to($user->email)->send($email);

Đính kèm file trong email

Đôi khi, trong một email chúng ta cũng muốn đính kèm các file như một file văn bản hoặc file slide trình bày vấn đề gì đó… việc này khá đơn giản trong Laravel Mail, sử dụng phương thức attach() để đính kèm những gì bạn muốn:

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

đầu vào của phương thức attach() có thể là một mảng các thông số cho file đính kèm:

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.user.registered')
                    ->attach('/path/to/file', [
                        'as' => 'Bigsale2017.pdf',
                        'mime' => 'application/pdf',
                    ]);
    }

Gửi email trong Laravel Mail

Sử dụng phương thức to() của facade Mail để thiết lập người cần gửi email đến, đầu vào của phương thức này có thể là một địa chỉ email, một instance của User hoặc một tập hợp các instance của User. Phương thức to() tự động lấy thuộc tính email và name trong đối tượng truyền vào để gửi email. Cuối cùng, sử dụng phương thức send() với đầu vào là một instance của class Mailables.

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

class RegisterController extends Controller
{    
   ...
    /**
    * Handle a registration request for the application.
    *
    * @param \Illuminate\Http\Request $request
    * @return \Illuminate\Http\Response
    */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();
        event(new Registered($user = $this->create($request->all())));
        $this->guard()->login($user);

        // Gui email khi nguoi dung dang ky
        $email = new WelcomeEmail($this->user);
        Mail::to($this->user->email)
            ->cc('admin1@allaravel.com')
            ->bcc('admin2@allaravel.com')
            ->send($email);

        return view('auth.welcome');
    }

Chúng ta cũng có thể sử dụng cc() và bcc() cho các tính năng gửi email cho những người khác.

Ví dụ gửi mail bằng Laravel Mail

Không cần nói nhiều về tác dụng của các ví dụ thực tế, hôm nay tôi sẽ đưa các bạn đến một ví dụ rất thú vị và sử dụng rất nhiều trong các hệ thống website. Trong hệ thống website cần một tính năng gửi email đến các thành viên khi có một tin tức mới, một sản phẩm mới hoặc một chương trình quảng cáo. Chúng ta sẽ thực hiện tính năng gửi mail này với Gmail, MailGun, MailTrap, Sparkpost, SES… Bắt đầu thực hành nào!

Đầu tiên, chúng ta sẽ tạo ra một Controller chứa toàn bộ mã liên quan đến xử lý mail (Xêm thêm Laravel Controller).

php artisan make:controller MailController

Câu lệnh này sẽ tạo ra MailController.php trong app\Http\Controllers. Tiếp theo thêm route vào routes/web.php:

Route::get('admin/email', 'MailController@showEmailForm');
Route::post('admin/email', 'MailController@sendEmail');

Tạo phương thức showEmailForm() trong MailController:

    public function showEmailForm(){
        return view('fontend.send-email');
    }

Phương thức này trả về view send-email, chúng ta thực hiện tạo view này trong resources/views/fontend.

@extends('layouts.default')

@section('title', 'Gửi email - Allaravel.com')

@section('content')
    {!! Form::open(array('url' => '/admin/email', 'class' => 'form-horizontal')) !!}
        <div class="form-group">
            {!! Form::label('name', 'Email service', array('class' => 'col-sm-3 control-label')) !!}
            <div class="col-sm-3">
                {!! Form::select('email_service', array('1' => 'SMTP GMAIL', '2' => 'MailGun', '3' => 'MailTrap', '4' => 'Sparkpost'), '1', array('class' => 'form-control')) !!}
            </div>
        </div>

        <div class="form-group">
            {!! Form::label('name', 'Tên người gửi', array('class' => 'col-sm-3 control-label')) !!}
            <div class="col-sm-9">
                {!! Form::text('from_name', 'All Laravel Admin', array('class' => 'form-control')) !!}
            </div>
        </div>

        <div class="form-group">
            {!! Form::label('from_email', 'Email người gửi', array('class' => 'col-sm-3 control-label')) !!}
            <div class="col-sm-9">
                {!! Form::text('from_email', 'allaravel.com@gmail.com', array('class' => 'form-control')) !!}
            </div>
        </div>

        <div class="form-group">
            {!! Form::label('to_email', 'Email người nhận', array('class' => 'col-sm-3 control-label')) !!}
            <div class="col-sm-9">
                {!! Form::text('to_email', '', array('class' => 'form-control')) !!}
            </div>
        </div>

        <div class="form-group">
            {!! Form::label('subject', 'Tiêu đề', array('class' => 'col-sm-3 control-label')) !!}
            <div class="col-sm-9">
                {!! Form::text('subject', '', array('class' => 'form-control')) !!}
            </div>
        </div>

        <div class="form-group">
            {!! Form::label('body', 'Nội dung', array('class' => 'col-sm-3 control-label')) !!}
            <div class="col-sm-9">
                {!! Form::textarea('body', '', array('class' => 'form-control', 'rows' => 5)) !!}
            </div>
        </div>

        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                {!! Form::submit('Gửi email', array('class' => 'btn btn-success')) !!}
            </div>
        </div>
   {!! Form::close() !!}
@endsection

Vào http://laravel.dev/admin/email chúng ta có giao diện như sau:

Ví dụ sử dụng Laravel Mail

Thêm phương thức sendEmail() vào MailController để xử lý việc gửi email:

Trường email người nhận sẽ được nhập vào theo kiểu email1@gmail.com, email2@gmail.com,… do đó để kiểm tra được tất cả các email này xem có đúng định dạng email không, chúng ta phải viết một hàm kiểm tra riêng (Xem Laravel Validate để biết thêm cách kiểm tra dữ liệu nhập vào). Để làm được việc này, chúng ta sử dụng phương thức extend() của facade Validator trong service provider mặc định AppServiceProvider (trong app\Providers).

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Validator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Validator::extend("emails", function($attribute, $value, $parameters) {
            $rules = [
                'email' => 'required|email',
            ];
            foreach ($value as $email) {
                $data = [
                    'email' => $email
                ];
                $validator = Validator::make($data, $rules);
                if ($validator->fails()) {
                    return false;
                }
            }
            return true;
        });
    }

Ok, chúng ta có thể sử dụng mở rộng này để kiểm tra trường nhập các email cần gửi như sau:

'to_email' => 'required|emails'

Quay lại với phương thức sendEmail trong MailController.

      public function sendEmail(Request $request){

	    $messages = [
	        'required' => 'Trường :attribute bắt buộc nhập.',
	        'email'    => 'Trường :attribute phải có định dạng email'
	    ];
	    $validator = Validator::make($request->all(), [
	        'to_email' => 'required|emails',
	        'subject'  => 'required',
	        'body'     => 'required'
	    ], $messages);

	    if ($validator->fails()) {
	        return redirect('admin/email')
	                ->withErrors($validator)
	                ->withInput();
	    } else {
	        $email_service = $request->input('email_service');
	        
	        switch ($email_service) {
	            case 1:
	                // Gmail
	                config(['mail.driver' => 'smtp', 'mail.host' => 'smtp.gmail.com', 'mail.port' => 587, 'mail.username' => 'allaravel.com@gmail.com', 'mail.password' => 'extpoeeoicypxqoie', 'mail.encryption' => 'tls']);
	                break;
	            
	            case 2:
	                // MailGun
	                config(['mail.driver' => 'mailgun']);
	                break;

	            case 3:
	                // MailTrap
	                config(['mail.driver' => 'smtp', 'mail.host' => 'smtp.mailtrap.io', 'mail.port' => 2525, 'mail.username' => '8b5309fif50e74', 'mail.password' => '39ov4ee432ab5c', 'mail.encryption' => 'tls']);

	                break;

	            case 4:
	                // Sparkpost
	                config(['mail.driver' => 'sparkpost', 'mail.host' => 'smtp.sparkpostmail.com', 'mail.port' => 587, 'mail.username' => 'SMTP_Injection', 'mail.password' => 'f57d09lc09a02650d816df6f501409odpkvjcjc6', 'mail.encryption' => 'STARTTLS']);

	                break;
	        }
	        
	        $email_arr = explode(',', $request->input('to_email'));
	        for ($i=0; $i < count($email_arr); $i++) { 
	        	$mailable = new SendEmail();
	        	Mail::to($email_arr[$i])->send($mailable);
	        }
	        
	        return redirect('admin/email')
	                ->with('message', 'Send email success!');
	    }
	}

Trong các cấu hình email ở trên chúng ta cần để ý một số vấn đề như sau:

  • Với Gmail, bạn có thể đưa mật khẩu vào MAIL_PASSWORD, tuy nhiên nếu tài khoản đăng bật chế độ 2 bước xác thực thì phải tạo Mật khẩu ứng dụng (app password) rồi đưa vào MAIL_PASSWORD. Khuyến cáo, bạn lên bật chế độ 2 bước xác thực với tài khoản Google để bảo mật. Cách tạo app password cho Google bạn có thể xem tại Sign in using App password hoặc xem trong ví dụ gửi email bằng hàng đợi trong bài Laravel Queue.
  • Với MailTrap, chúng ta tạo tài khoản, sau đó vào inbox sẽ có các thông tin cần thiết như host, port, username, password, encryption.

Thiết lập Laravel Mail với MailTrap

  • Với SparkPost, chúng ta cũng đăng ký, sau đó trong phần quản trị vào Account -> SMTP delay, các thông tin để thiết lập cho mail cũng ở hết trong này.

Thiết lập Laravel Mail với Sparkpost

Tiếp theo chúng ta tạo một class Mailables bằng lệnh artisan:

php artisan make:mail SendEmail

Phương thức build() trong ví dụ, chúng ta không truyền dữ liệu vào mà chỉ đơn giản trả về giao diện của email.

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendEmail 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->view('email.info-email');
    }
}

Tạo view cho email (resources/views/email/info-email.blade.php) với nội dung đơn giản như sau:

<!DOCTYPE html>
<html>
<head>
    <title>Test Email</title>
</head>
<body>
    <h1>Test Email</h1>
</body>
</html>

Ok, chúng ta đã chuẩn bị xong toàn bộ code để thực hành ví dụ Laravel Mail. Thực hiện thử các dịch vụ mail và kiểm tra email nhận được. Chúc các bạn thực hiện thành công.

21 thoughts on “Xử lý thư điện tử với Laravel Mail

    1. Lỗi này liên quan đến certificate, bạn điều chỉnh file php.ini tìm đến phần cURL và thiết lập lại như sau:
      [curl]
      ; A default value for the CURLOPT_CAINFO option. This is required to be an
      ; absolute path.
      curl.cainfo = “C:/xampp/apache/bin/curl-ca-bundle.crt”
      Sau đó save file và khởi động lại Apache.

        1. Lỗi này do tài khoản Gmail (Google) của bạn đang bật chế độ xác thực hai bước (bước 1 là mật khẩu thường, bước 2 là mã xác thực qua tin nhắn SMS) do đó khả năng phần mail.password bạn đang để là mật khẩu bước 1 đúng không?

        2. Bạn chịu khó đọc kỹ bài viết nhé, mình đã biên tập rất kỹ mà, giờ bạn vào thực hiện tạo mật khẩu ứng dụng cho tài khoản Google, sau đó đưa cái mật khẩu ứng dụng vào mail.password. Bạn tham khảo trong bài Laravel Queue ở phần gần cuối mình có hướng dẫn chi tiết cách bật chế độ 2 bước xác thực và tạo app password đấy.

    1. Đăng ký Mailgun đòi hỏi nhập thẻ tín dụng, mình chưa đăng ký được, để tối nay tạo thử cái virtual visa xem nó có chấp nhận không

  1. gửi mail bằng Gmail em làm được rồi, chạy rất ổn. sao phần mailtrap em thực hiện xong mà check email không thấy cái mail nào gửi đến nhỉ, không thấy bất kỳ thông báo lỗi nào cả?

    1. MailTrap để test email cho developer thôi bạn, nó không phải gửi đến các email bạn đưa vào mà nó gửi lên inbox của tài khoản mailtrap. Bạn vào sẽ thấy tất cả email phi vào đây. Cái này dùng để test xem hệ thống gửi email ok không, mẫu thư gửi đi có ổn không thôi.

  2. Cho mình hỏi để gửi được mail thì bắt buộc phải tắt chế độ xác thực 2 bước đi hả bạn ?? Mình dùng Swifmailler và bị lỗi giống như An An

  3. Chắc phải tắt chế độ xác thực 2 bước, chứ không lấy code từ google rồi đưa vào ứng dụng kiểu gì được, có anh em nào giải quyết được vấn đề này không?

  4. Mình bị lỗi này khi gửi với gmail:
    stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
    Ai fix dùm mình với.

  5. Add ơi có cách nào tùy biến cấu hình mail gửi trong file .env ko nhỉ! trong db e có 3 cái gmail hệ thống gồm sale, kế toán, kỹ thuật. khi quản trị viên gửi mail thì họ sẽ chọn 1 trong 3 cái mail đấy để gửi đi!

Add Comment