├── LICENSE ├── README.md ├── composer.json ├── config └── services.php └── src ├── DirectMailException.php ├── DirectMailTransport.php └── DirectMailTransportProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 WangYan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DirectMail 2 | 3 | 使用阿里云的邮件推送(DirectMail)发送邮件。 4 | 5 | 需要 Laravel 5.5+ ,目前仅支持单一发信接口,后续会支持批量发信接口。 6 | 7 | 优点是非常简洁,没有引入阿里云全家桶,使用 laravel mailables 发送邮件。 8 | 9 | > 邮件推送(DirectMail)是一款简单高效的电子邮件发送服务,它构建在可靠稳定的阿里云基础之上,帮助您快速、精准地实现事务邮件、通知邮件和批量邮件的发送。邮件推送历经两年双11考验,在发送速度、系统稳定性和到达率上表现优异;提供丰富的接口和灵活的使用方式,为企业和开发者解决邮件投递的难题,用户无需自建邮件服务器,开通服务即可享受阿里云优质的邮件服务,获得邮件投递的最佳实践。 10 | 11 | > DirectMail 官网: 12 | 13 | ## 安装 14 | 15 | 在项目目录下执行 16 | 17 | ```bash 18 | composer require wang_yan/directmail:dev-master 19 | ``` 20 | 21 | ## 配置 22 | 23 | 修改 `config/app.php`,添加服务提供者 24 | 25 | ```php 26 | [ 28 | // 添加这行 29 | WangYan\DirectMail\DirectMailTransportProvider::class, 30 | ]; 31 | ``` 32 | 33 | 在 `.env` 中配置你的密钥, 并修改邮件驱动为 `directmail` 34 | 35 | ```bash 36 | MAIL_DRIVER=directmail 37 | 38 | DIRECT_MAIL_KEY= # AccessKeyId 39 | DIRECT_MAIL_SECRET= # AccessSecret 40 | ``` 41 | 42 | ## 使用 43 | 44 | 详细用法请参考 laravel 文档: 45 | 46 | > 47 | 48 | 使用演示: 49 | 50 | ```php 51 | 'https://laravel.com', 56 | 'name' => 'laravel' 57 | ]; 58 | 59 | Mail::send('emails.register', $data, function ($message) { 60 | $message->from('us@example.com', 'Laravel'); 61 | $message->to('foo@example.com'); 62 | $message->subject('Hello World'); 63 | }); 64 | }); 65 | ``` 66 | 67 | ## 实例 68 | 69 | 演示怎样发送注册验证邮件,首先初始化 70 | 71 | ```bash 72 | php artisan make:auth 73 | php artisan migrate 74 | ``` 75 | 76 | 修改 `RegisterController` 控制器 77 | 78 | ```bash 79 | $data['name'], 88 | 'email' => $data['email'], 89 | // 数据库 users 表要有 token 字段 90 | 'token' => str_random(30), 91 | 'password' => bcrypt($data['password']), 92 | ]); 93 | 94 | $this->sendVerifyEmail($user); 95 | return $user; 96 | } 97 | 98 | private function sendVerifyEmail($user) 99 | { 100 | $data = [ 101 | 'name' => $user->name, 102 | 'url' => Route('email.verify',['token' => $user->token]) 103 | ]; 104 | Mail::send('emails.register', $data, function ($message) use ($user) { 105 | $message->from('service@dm.mail.wangyan.org', env('APP_NAME','Laravel')); 106 | $message->to($user->email); 107 | $message->subject('请验证您的 Email 地址'); 108 | }); 109 | } 110 | ``` 111 | 112 | 修改 User 模型 113 | 114 | ```bash 115 | name('email.verify'); 134 | ``` 135 | 136 | 增加控制器 137 | 138 | ```bash 139 | php artisan make:controller EmailController 140 | ``` 141 | 142 | 编辑控制器 143 | 144 | ```php 145 | first(); 155 | 156 | if(is_null($user)){ 157 | return redirect('/'); 158 | } 159 | 160 | $user->is_active = 1; 161 | 162 | $user->token= str_random(30); 163 | $user->save(); 164 | 165 | return redirect('/home'); 166 | } 167 | } 168 | ``` 169 | 170 | ## 贡献 171 | 172 | - 173 | - -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wang_yan/directmail", 3 | "license": "MIT", 4 | "authors": [ 5 | { 6 | "name": "WangYan", 7 | "email": "i@wangyan.org" 8 | } 9 | ], 10 | "require": { 11 | "php": ">=5.6", 12 | "illuminate/support": "^5.5", 13 | "guzzlehttp/guzzle": "^6.3" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "WangYan\\DirectMail\\": "src/" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'AccessKeyId' => env('DIRECT_MAIL_KEY'), 5 | 'AccessSecret' => env('DIRECT_MAIL_SECRET'), 6 | 'ReplyToAddress' => env('DIRECT_MAIL_REPLY','true'), 7 | 'AddressType' => env('DIRECT_MAIL_ADDRESS_TYPE','1'), 8 | ], 9 | ]; 10 | -------------------------------------------------------------------------------- /src/DirectMailException.php: -------------------------------------------------------------------------------- 1 | setMessage($message)); 19 | } 20 | 21 | /** 22 | * 翻译错误提示代码 23 | * @param $message 24 | * @return string 25 | */ 26 | private function setMessage($message) 27 | { 28 | switch ($message): 29 | case 'InvalidMailAddress.NotFound': 30 | $this->message = "发信地址不存在"; 31 | break; 32 | case 'InvalidMailAddressStatus.Malformed': 33 | $this->message = "发信地址状态不正确"; 34 | break; 35 | case 'InvalidToAddress': 36 | $this->message = "目标地址不正确"; 37 | break; 38 | case 'InvalidBody': 39 | $this->message = "邮件正文不正确。textBody 或 htmlBody不能同时为空"; 40 | break; 41 | case 'InvalidSendMail.Spam': 42 | $this->message = "本次发送操作被反垃圾系统检测为垃圾邮件,禁止发送。请仔细检查邮件内容和域名状态等"; 43 | break; 44 | case 'InvalidSubject.Malformed': 45 | $this->message = "邮件主题限制在100个字符以内"; 46 | break; 47 | case 'InvalidMailAddressDomain.Malformed': 48 | $this->message = "发信地址的域名状态不正确,请检查MX、SPF配置是否正确"; 49 | break; 50 | case 'InvalidFromALias.Malformed': 51 | $this->message = "发信人昵称不正确,请检查发信人昵称是否正确,长度小于15个字符。"; 52 | break; 53 | case 'InvalidTimeStamp.Expired': 54 | $this->message = "时间戳错误,请检查 timezone 是否为 UTC"; 55 | break; 56 | default: 57 | $this->message = $message; 58 | endswitch; 59 | 60 | return $this->message; 61 | } 62 | } -------------------------------------------------------------------------------- /src/DirectMailTransport.php: -------------------------------------------------------------------------------- 1 | AccessKeyId = $AccessKeyId; 31 | $this->AccessSecret = $AccessSecret; 32 | $this->ReplyToAddress = $ReplyToAddress; 33 | $this->AddressType = $AddressType; 34 | 35 | $this->CommonParameters['Format'] = 'JSON'; 36 | $this->CommonParameters['Version'] = '2015-11-23'; 37 | $this->CommonParameters['AccessKeyId'] = $AccessKeyId; 38 | $this->CommonParameters['SignatureMethod'] = 'HMAC-SHA1'; 39 | $this->CommonParameters['Timestamp'] = date('Y-m-d\TH:i:s\Z'); 40 | $this->CommonParameters['SignatureVersion'] = '1.0'; 41 | $this->CommonParameters['SignatureNonce'] = uniqid(); 42 | } 43 | 44 | /** 45 | * 调用单一发信接口或者批量发信接口发信 46 | * 47 | * @param Swift_Mime_SimpleMessage $message 48 | * @param null $failedRecipients 49 | * @return mixed 50 | */ 51 | public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) 52 | { 53 | $body = $message->getBody(); 54 | 55 | if ($body instanceof BatchSendMail) { 56 | // $result = $this->BatchSendMail($message); 57 | } else { 58 | $result = $this->SingleSendMail($message); 59 | } 60 | 61 | return $result; 62 | } 63 | 64 | /** 65 | * 使用单一发信接口发信 66 | * 67 | * @param Swift_Mime_SimpleMessage $message 68 | * @return ResponseInterface 69 | */ 70 | private function SingleSendMail(Swift_Mime_SimpleMessage $message) 71 | { 72 | // 单一发信接口参数 73 | $this->SingleParameters['Action'] = 'SingleSendMail'; 74 | $this->SingleParameters['AccountName'] = $this->getAddress($message->getFrom()); 75 | $this->SingleParameters['ReplyToAddress'] = (string) $this->ReplyToAddress; 76 | $this->SingleParameters['AddressType'] = (string) $this->AddressType; 77 | $this->SingleParameters['ToAddress'] = $this->getAddress($message->getTo()); 78 | $this->SingleParameters['FromAlias'] = $this->getFromName($message); 79 | $this->SingleParameters['Subject'] = $message->getSubject(); 80 | $this->SingleParameters['HtmlBody'] = $message->getBody(); 81 | 82 | $Parameters = array_merge($this->CommonParameters,$this->SingleParameters); 83 | $Parameters['Signature'] = $this->makeSign($Parameters); 84 | 85 | $Http = new Client(); 86 | try { 87 | $Response = $Http->post(self::API_URL, [ 88 | 'form_params' => $Parameters, 89 | ]); 90 | } catch (ClientException $e) { 91 | return $this->response($e->getResponse()); 92 | } 93 | 94 | return $this->response($Response); 95 | } 96 | 97 | /** 98 | * 获取发信地址或目标地址 99 | * 该地址需要先在阿里云管理控制台中配置。 100 | * 101 | * @param $data 102 | * @return null|string 103 | */ 104 | private function getAddress($data) 105 | { 106 | if (!$data) { 107 | return; 108 | } 109 | return array_get(array_keys($data), 0, null); 110 | } 111 | 112 | /** 113 | * 获取发信人昵称 114 | * 发信人昵称长度小于15个字符 115 | * 116 | * @param Swift_Mime_SimpleMessage $message 117 | * @return mixed 118 | */ 119 | private function getFromName(Swift_Mime_SimpleMessage $message) 120 | { 121 | return array_get(array_values($message->getFrom()), 0); 122 | } 123 | 124 | /** 125 | * 生成请求签名(Signature)信息 126 | * 127 | * @param $Parameters 128 | * @return string 129 | */ 130 | private function makeSign($Parameters) 131 | { 132 | ksort($Parameters); 133 | $CanonicalizedQueryString = ''; 134 | foreach ($Parameters as $key => $value) { 135 | $CanonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value); 136 | } 137 | $StringToSign = 'POST&%2F&' . $this->percentEncode(substr($CanonicalizedQueryString, 1)); 138 | $Signature = hash_hmac('sha1', $StringToSign, $this->AccessSecret . "&", true); 139 | 140 | $Signature = base64_encode($Signature); 141 | return $Signature; 142 | } 143 | 144 | /** 145 | * 构造规范化字符串用于计算签名(Signature) 146 | * 147 | * @param $str 148 | * @return mixed|string 149 | */ 150 | private function percentEncode($str) 151 | { 152 | $res = urlencode($str); 153 | $res = preg_replace('/\+/', '%20', $res); 154 | $res = preg_replace('/\*/', '%2A', $res); 155 | $res = preg_replace('/%7E/', '~', $res); 156 | return $res; 157 | } 158 | 159 | 160 | /** 161 | * 解析 DirectMail 返回值,失败抛出异常 162 | * 163 | * @param ResponseInterface $response 164 | * @return bool 165 | * @throws DirectMailException 166 | */ 167 | protected function response(ResponseInterface $response) 168 | { 169 | // Psr7\str($response); 170 | $StatusCode = $response->getStatusCode(); 171 | $Body = json_decode($response->getBody()); 172 | 173 | if ($StatusCode != 200) { 174 | throw new DirectMailException($StatusCode,$Body->Code); 175 | } 176 | 177 | return true; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/DirectMailTransportProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 28 | dirname(__DIR__).'/config/services.php', 'services' 29 | ); 30 | 31 | $this->app->resolving('swift.transport', function (TransportManager $tm) { 32 | $tm->extend('directmail', function () { 33 | $AccessKeyId = config('services.directmail.AccessKeyId'); 34 | $AccessSecret = config('services.directmail.AccessSecret'); 35 | $ReplyToAddress = config('services.directmail.ReplyToAddress'); 36 | $AddressType = config('services.directmail.AddressType'); 37 | 38 | return new DirectMailTransport($AccessKeyId, $AccessSecret,$ReplyToAddress,$AddressType); 39 | }); 40 | }); 41 | } 42 | } 43 | --------------------------------------------------------------------------------