11/08/2018, 20:57

Send mail with Laravel, gmail

Như chúng ta đã biết việc gửi mail về cho người dùng trong web là một vấn đề phổ biến. Điển hình như gửi confirm mail khi member register, reset password khi member quên mật khẩu, hoặc là một thông báo gì đó mà cần dùng đến mail để thông báo cho người dùng. Hôm nay mình xin share cách gửi ...

Như chúng ta đã biết việc gửi mail về cho người dùng trong web là một vấn đề phổ biến.

Điển hình như gửi confirm mail khi member register, reset password khi member quên mật khẩu, hoặc là một thông báo gì đó mà cần dùng đến mail để thông báo cho người dùng.

Hôm nay mình xin share cách gửi mail với Laravel. Cụ thể là Laravel 5.6 và gmail.

a. Edit file .env

Bạn tìm đến file .env và tìm tương ứng và sửa. Nếu chưa có thì có thể tạo thêm

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587 
MAIL_USERNAME=thaivd@hblab.vn
MAIL_PASSWORD=*******
MAIL_ENCRYPTION=tls

Với gmail thì chúng ta dùng phương thức là smtp

MAIL_USERNAME là cái mail để gửi From, ví như mail: admin-project@gmail.com

Ở phần MAIL_PASSWORD bạn điền pass của mail MAIL_USERNAME, ví dụ 123456. Mình đang để ****** :D.

Hoặc nếu không dùng .env thì phải sửa trong config/mail.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | 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", "mail", "sendmail", "mailgun", "mandrill",
    |            "ses", "sparkpost", "log"
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | SMTP Host Address
    |--------------------------------------------------------------------------
    |
    | Here you may provide the host address of the SMTP server used by your
    | applications. A default option is provided that is compatible with
    | the Mailgun mail service which will provide reliable deliveries.
    |
    */

    'host' => env('MAIL_HOST', 'smtp.mailgun.org'),

    /*
    |--------------------------------------------------------------------------
    | SMTP Host Port
    |--------------------------------------------------------------------------
    |
    | This is the SMTP port used by your application to deliver e-mails to
    | users of the application. Like the host we have set this value to
    | stay compatible with the Mailgun e-mail application by default.
    |
    */

    'port' => env('MAIL_PORT', 587),

    /*
    |--------------------------------------------------------------------------
    | Global "From" Address
    |--------------------------------------------------------------------------
    |
    | You may wish for all e-mails sent by your application to be sent from
    | the same address. Here, you may specify a name and address that is
    | used globally for all e-mails that are sent by your application.
    |
    */

    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
        'name' => env('MAIL_FROM_NAME', 'Example'),
    ],

    /*
    |--------------------------------------------------------------------------
    | E-Mail Encryption Protocol
    |--------------------------------------------------------------------------
    |
    | Here you may specify the encryption protocol that should be used when
    | the application send e-mail messages. A sensible default using the
    | transport layer security protocol should provide great security.
    |
    */

    'encryption' => env('MAIL_ENCRYPTION', 'tls'),

    /*
    |--------------------------------------------------------------------------
    | SMTP Server Username
    |--------------------------------------------------------------------------
    |
    | If your SMTP server requires a username for authentication, you should
    | set it here. This will get used to authenticate with your server on
    | connection. You may also set the "password" value below this one.
    |
    */

    'username' => env('MAIL_USERNAME'),

    /*
    |--------------------------------------------------------------------------
    | SMTP Server Password
    |--------------------------------------------------------------------------
    |
    | Here you may set the password required by your SMTP server to send out
    | messages from your application. This will be given to the server on
    | connection so that the application will be able to send messages.
    |
    */

    'password' => env('MAIL_PASSWORD'),

    /*
    |--------------------------------------------------------------------------
    | Sendmail System Path
    |--------------------------------------------------------------------------
    |
    | When using the "sendmail" driver to send e-mails, we will need to know
    | the path to where Sendmail lives on this server. A default path has
    | been provided here, which will work well on most of your systems.
    |
    */

    'sendmail' => '/usr/sbin/sendmail -bs',

];

Hãy đảm bảo mail MAIL_USERNAME đang bật chế độ xác thực 2 bước trong Google.
Nó hướng dẫn chi tiết rồi.

Ngoài ra trong .env chúng ta cần thiết lập thêm

QUEUE_DRIVER=database

b. Thử với reset password

Okie, giả sử có đường dẫn thế này /password_remind

Thằng router ở web.php có

Route::get('/password_remind',
    'MemberAuthPasswordRemindController@showLinkRequestForm')->name('password_remind');
Route::post('/password_remind', 'MemberAuthPasswordRemindController@sendNewPass');

View app/member/auth/sendNewPassCompleteMail.php cũng có

 { { $name}}様<br><br>
「電話占いカリヨン」をご利用いただき、誠にありがとうございます。<br>
    パスワードを再発行いたしましたのでご確認ください。<br>
<br>
電話番号: { {  $phone }}<br>
パスワード: { { $password}}<br>
<br>
▼ログインはこちら(カリヨンTOPページ)<br>

Đấy, cái mail gửi về gồm có tên (name), số điện thoại đăng nhập (phone) và mật khẩu mới (password).

Vào Controller PasswordRemindController.php có gì

public function sendNewPass($identity)
    {
        $model = $this->model->findUserPasswordReset($identity);

        if(!$model || $model->checkActiveResetPassword()){
            return false;
        }
        try{
            $password = randomNewPass(6);
            $model->resetPassword($password);
            Mail::to($model->getEmail())->send(new SendNewPasswordComplete($model->phone, $password, $model->name));
        }catch (Exception $e){
            throw new MailException('progress.sentMailError');
        }

        return config('const.statusSuccess');
    }

Đọc đoạn code trên thì khá clear, trước hết ta có $model sẽ lấy ra User cần đổi pass.

Sẽ tạo ra một mật khẩu mới random 6 số. $password = randomNewPass(6);

Rồi truyền các tham số của $model (name, phone) và $password đến SendNewPasswordComplete. Tại sao lại tạo ra SendNewPasswordComplete. Đơn giản nó có thể dùng cho những cái khác.

Ta tạo một file SendNewPasswordComplete.php

<?php

namespace AppMail;

use IlluminateBusQueueable;
use IlluminateMailMailable;
use IlluminateQueueSerializesModels;
use IlluminateContractsQueueShouldQueue;

class SendNewPasswordComplete extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @param $phone
     * @param $password
     * @param null $name
     */
    public function __construct($phone, $password, $name = null)
    {
        $this->phone = $phone;
        $this->password = $password;
        $this->name = $name;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('パスワード再発行 | 電話占いカリヨン')
            ->view('app.member.auth.sendNewPassCompleteMail')->with([
                'phone' => $this->phone,
                'password' => $this->password,
                'name' => $this->name
            ]);
    }
}

Câu chuyện là khi gửi mail SMTP thì có thể xảy ra tình trạng gửi mail chậm. Bạn tưởng tượng mình là người dùng. Bấm cái nút xác nhận gửi lại cái mật khẩu mà cái web nó quay quay mất 4 5s xem. Muốn out ngay.

Cũng có nhiều nguyên nhân dẫn đến tình trạng đó: SMTP Server, dung lượng mail, đường truyền, ....

Để giải quyết vấn đề này chúng ta sử dụng queues

Hiểu một cách đơn giản là nó như hàng đợi. Khi User click vào nút send mail thì thay vì để nó xoay vòng vòng đợi xử lý xong thì nó chuyển thẳng vào queue. Đợi queue xử lý xong là mail được bắn về cho mình.

  • Tạo và thêm Job vào queue Bình thường các job sẽ được tạo trong appJobs, để tạo mới thì php artisan make:job sendNewPassMail alt text
  • Để thêm job vào một queue, sử dụng phương thức dispatch():

Trước trong PasswordRemindController.php

public function sendNewPass($identity)
    {
        Mail::to($model->getEmail())->send(new SendNewPasswordComplete($model->phone, $password, $model->name));
    }



Sửa thành

public function sendNewPass($identity)
    {
        dispatch(new AppJobssendNewPassMail($model));
    }

Câu chuyện oái oăm: Bản thân thằng queue sẽ lấy dữ liệu từ Model ra. Mà thằng Password thì phải hashing khi lưu vào Database. Thành ra tạo thêm một trường real_password trong bảng member (Dùng migration mà thêm).

Sẽ thành

public function sendNewPass($identity)
    {
        $model = $this->model->findUserPasswordReset($identity);

        if(!$model || $model->checkActiveResetPassword()){
            return false;
        }
        try{
            $password = randomNewPass(6);
            $model->resetPassword($password);
            $model->real_password = $password;
            $model->save();
            dispatch(new AppJobssendNewPassMail($model));
        }catch (Exception $e){
            throw new MailException('progress.sentMailError');
        }

        return config('const.statusSuccess');
    }

Xử lý ở sendNewPassMail.php, chú ý ở handle()

<?php

namespace AppJobs;

use AppPodcast;
use AppAudioProcessor;
use IlluminateBusQueueable;
use IlluminateQueueSerializesModels;
use IlluminateQueueInteractsWithQueue;
use IlluminateContractsQueueShouldQueue;
use IlluminateSupportFacadesMail;
use AppMailSendNewPasswordComplete;

class sendNewPassMail implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    protected $model;

    /**
     * Create a new job instance.
     *
     * @param  Podcast  $podcast
     * @return void
     */
    public function __construct(AppModelsMember $model)
    {
        $this->model = $model;
    }

    /**
     * Execute the job.
     *
     * @param  AudioProcessor  $processor
     * @return void
     */
    public function handle()
    {
        Mail::to($this->model->getEmail())->send(new SendNewPasswordComplete($this->model->phone, $this->model->real_password, $this->model->name));
        $this->model->real_password = null;
        $this->model->save();
    }
}

Đừng quên $this->model->real_password = null;, khi gửi mail rồi nhớ set real_password về Null tránh trường hợp lộ Pass.

  • Thực thi các jobs trong queue

Một số lệnh cần lưu lý:

Mặc định job sẽ được thực hiện 1 lần, nếu lỗi sẽ được bỏ qua, nếu muốn tăng lên 3 thì

php artisan queue:work --tries=3

Bạn có thể thiết lập thời gian timeout của các job bằng cách sử dụng câu lệnh artisan

php artisan queue:work --timeout=60
  • Vấn đề quan trọng: chạy ngầm queue.

Chẳng thể khi nào để chuyện khách hàng chuẩn bị nhấn nút Send mail chúng ta lại chạy php artisan queue:work cả.

Cũng có vài cách xử lý cho việc queue chạy ngầm.

  1. supervisor
  2. Bạn vào file app/Console/Kernel.php->Kernel->schedule(): và thêm
$schedule->command('queue:work --daemon --once')->withoutOverlapping();

Xin hết!

Cũng là lần đầu làm chuyện này. Nên nếu có sai sót hoặc trong những bước mình xử lý, anh em có cách nào hay hơn, hiệu quả hơn thì cứ comment.

Bài Bái

(。◕‿◕。) NyLaa (。◕‿◕。)

0