前些天帮助朋友弄了一个简单的网站,上线以后发现朋友用的是百度推广送的一个主机空间,PHP 版本太低,甚至不支持邮件发送功能。没有办法只有将整个后台部署到朋友自己的服务器,使用 Laravel API 配置了一个邮件接口,前台网站使用 CURL 请求这个接口发送邮件。

以下是从编码到测试以及域名配置的所有流程。

创建项目

首先我们准备好一个 Laravel 项目:

larvel new mail-demo
// or
composer create-project --prefer-dist laravel/laravel mail-demo

完成安装后,启动内置的 Server 运行:

sudo php artisan serve --host=127.0.0.1 --port=80

配置邮件发送服务

Laravel 支持数种不同的邮件驱动,比如 mail() 函数使用的系统的 sendmail 程序,STMP 邮件服务器或者第三方的邮件商,如:Mailgun, SparkPost 等,这些需要安装额外的库,具体可以参考文档 邮件发送

打开 .env,将 MAIL_DRIVER 修改为 sendmail。

MAIL_DRIVER=sendmail
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

更多邮件的配置,比如默认驱动,发送人等等选项,可以在 config/mail.php 中设定。

路由器

打开 routes/api.php,注册一个 sendmail 路由,接受客户端发送的 POST 请求。

<?php
use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('sendmail', function(Request $request) {
    // sendmail here
});

我们可以直接把发送邮件的代码写在路由配置的匿名函数中,当然如果需要分离代码可以创建一个控制器。

php artisan make:controller MailController

代码如下:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MailController extends Controller
{
    public function sendmail(Request $request)
    {
        // sendmail here
    }
}

接着我们调整 routes/api.php 中的路由器,指向对应的控制器方法:

<?php
// .... other routers
Route::post('sendmail', 'MailController@sendmail');

Laravel 的 Restful API 有个 /api 前缀,所以我们的完整的访问路径为:

http://localhost/api/sendmail

认证

我的项目中并没有认证功能,因为并非开放的公用接口,但如果需要大体流程如下:

以后有空再写吧!

队列

如果邮件发送非常频繁可以考虑使用队列功能来延迟发送,降低系统的负载。

Laravel 的队列类型(连接)包含几种不同的驱动,默认使用 sync,其它的还有数据库, Beanstalkd, Amazon SQS, Redis 等等。

队列的具体配置可以查看 config/queue.php,或者参考文档 队列系统

连接与队列的概念是不同的,config/queue.php 中的 connections 指定了不同的驱动类型,Laravel 会为每个驱动创建不同的特殊连接,而队列则是表示队列任务。

如果不使用队列,发送邮件的代码可以直接写在控制器或者路由器配置中,否则我们就需要创建一个任务类:

php artisan make:job MailTask

创建好的任务类存放在 app/Jobs/MailTask.php:

<?php
namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Mail;

class MailTask implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

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

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
		$content = "Here is your mail content";

		Mail::raw($content, function ($message) {
            $message->from('noreply@your-domain.com', 'yourname');
            $message->to('someone@example.com');
            $message->subject('Test mail from laravel mail queue');
        });
    }
}

接着在控制器代码中,将 MailTask 类的实例传给 dispatch() 方法即可:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Jobs\MailTask;

class MailController extends Controller
{
    public function sendmail(Request $request)
    {
        $this->dispatch(new MailTask)
             ->onConnections('sync')    // 指定连接
             ->onQueue('emails');       // 指定队列
    }
}

接着就可以启动队列监听服务器:

php artisan queue:listen
// or
php artisan queue:work

使用 Postman 或者 CURL 测试:

curl -X POST http://your-domain.com/api/sendmail

邮件发垃圾配置

作为邮件的发送方,最好配置一下域名 SPF,以免从你的服务器送出的邮件,被目标服务器因无法区分是否伪造而当成垃圾邮件对待。

这里只说配置方法,假设你的域名是 example.com,那么你可以这样设置:

  1. 添加一条 A 记录,mail.example.com -> [xx.xx.xx.xx]
  2. 添加一条 MX 记录,@.example.com -> mail.example.com
  3. 添加一条 TXT 记录,v=spf1 mx mx:mail.example.com ~all

更多 SPF 的语法格式可以参考 http://www.openspf.org/SPF_Record_Syntax