Reading this article requires you to have a certain SES use base, if you don't understand, you can look at the discussion in this question http://segmentfault.com/q/1010000000095210
The full name of SES is the simple email service, which is a mail base for Amazon. As part of the AWS Foundation service, it inherits the traditional benefits of AWS-- cheaply . Yes, it's really very cheap. That's why I don't use mailgun or anything else that's better than the mail service. If you send 100,000 e-mails a month, the basic thing is to pay more than 10 dollars. This is a big price advantage for other services that start with hundreds of millions of knives. So, with this I can endure its many shortcomings. But as the number of people using SES increased at home, he was killed one day at the end of last year. So, I started to try to do a layer of proxy on my own server abroad to continue to use this service. It also provides an opportunity for me to make improvements to its API to achieve some of the more valuable features, such as mass mailing. Therefore I did not use the foreign server directly to do a reverse proxy to play, this only solves the superficial problem, but my expansion function's demand cannot realize. So I set two basic goals for designing this SES agent. Fully compatible with the original API interface, which means that the original code basically does not need to change the agent can be used to achieve bulk mail functionThe implementation of the 1th is very simple, in fact, the implementation of a reverse proxy PHP, sent over the parameters received, and then after the assembly using the curl component sent to the real SES server, obtain a receipt and then directly output to the client. This is a standard agent process, the following gives my code, the important part of which I have given the comments Note that this code needs to be placed in the root directory of the domain name, of course, the level two domain name can also
-
- Include __dir__. '/includes.php ';
- Here are some of the more important headers, others don't need attention
- $headers = Array (
- ' Date: '. Get_header (' Date '),
- ' Host: '. Ses_host,
- ' X-amzn-authorization: '. Get_header (' x-amzn-authorization ')
- );
- Then assemble the URL again to request this positive SES server
- $url = ' https://'. Ses_host. '/'
- . (Empty ($_server[' query_string ')? '' : '?' . $_server[' query_string ');
- $ch = Curl_init ();
- curl_setopt ($ch, curlopt_useragent, ' simpleemailservice/php ');
- curl_setopt ($ch, Curlopt_ssl_verifyhost, 1);
- curl_setopt ($ch, Curlopt_ssl_verifypeer, 1);
- Need to deal with the ' POST ' and ' DELETE ' method, the ' GET ' method is much more than I can achieve
- In fact, there are some ways to get the current information, which you can see directly in the background.
- Switch ($_server[' Request_method ')} {
- Case ' GET ':
- Break
- Case ' POST ':
- Global $HTTP _raw_post_data;
- $data = Empty ($HTTP _raw_post_data)? file_get_contents (' Php://input ')
- : $HTTP _raw_post_data;
- $headers [] = ' content-type:application/x-www-form-urlencoded ';
- Parse_data ($data);
- curl_setopt ($ch, Curlopt_customrequest, ' POST ');
- curl_setopt ($ch, Curlopt_postfields, $data);
- Break
- Case ' DELETE ':
- curl_setopt ($ch, Curlopt_customrequest, ' DELETE ');
- Break
- Default
- Break
- }
- curl_setopt ($ch, Curlopt_httpheader, $headers);
- curl_setopt ($ch, Curlopt_header, false);
- curl_setopt ($ch, Curlopt_url, $url);
- curl_setopt ($ch, Curlopt_returntransfer, true);
- curl_setopt ($ch, curlopt_followlocation, true);
- $response = curl_exec ($ch);
- $content _type = Curl_getinfo ($ch, Curlinfo_content_type);
- $status = Curl_getinfo ($ch, Curlinfo_http_code);
- Curl_close ($ch);
- Header (' Content-type: '. $content _type, True, $status);
- Echo $response;
Copy CodeThis piece of code is very simple, but there are some tricks to be aware of, where I work with the Post method using a private function called Parse_data, which is actually the key to implementing mass mailing. Speaking of which, I have to mention that the SES email api,ses only provides a simple mail-sending API, where its sending object supports multiple, but when you send to multiple recipients, it also sees the other recipient's address in the recipient bar. Of course it also supports CC or Bcc cc, but when you use this CC feature to implement mass mailings, the recipient will see that they are in the CC object, not among the recipients. For a regular website, these are obviously intolerable. So we need a real concurrency interface to send mail, knowing that SES assigns me a quota of 28 messages per second (different quotas per person), and 100,000 messages per hour if fully utilized, to meet the needs of midsize websites. So I came up with the idea that, without changing the client interface at all, I would send a single message from multiple recipients on the proxy server to multiple messages of one individual recipient, and then send those messages to SES on an asynchronous queue . This is what the Parse_data function does, and I will give you the code in includes.php, which contains all the private functions to be used, the previous define definition, please modify it according to your own requirements.
-
- Define (' Redis_host ', ' 127.0.0.1 ');
- Define (' Redis_port ', 6379);
- Define (' Ses_host ', ' email.us-east-1.amazonaws.com ');
- Define (' Ses_key ', ');
- Define (' Ses_secret ', ');
- /**
- * Get_header
- *
- * @param mixed $name
- * @access Public
- * @return void
- */
- function Get_header ($name) {
- $name = ' http_ '. Strtoupper (Str_replace ('-', ' _ ', $name));
- return Isset ($_server[$name])? $_server[$name]: ";
- }
- /**
- * MY_PARSE_STR
- *
- * @param mixed $query
- * @param mixed $params
- * @access Public
- * @return void
- */
- function My_parse_str ($query, & $params) {
- if (empty ($query)) {
- Return
- }
- $decode = function ($str) {
- Return Rawurldecode (Str_replace (' ~ ', '%7e ', $str));
- };
- $data = Explode (' & ', $query);
- $params = Array ();
- foreach ($data as $value) {
- List ($key, $val) = explode (' = ', $value, 2);
- if (Isset ($params [$key])) {
- if (!is_array ($params [$key])) {
- $params [$key] = Array ($params [$key]);
- }
- $params [$key] = $val;
- } else {
- $params [$key] = $decode ($val);
- }
- }
- }
- /**
- * My_urlencode
- *
- * @param mixed $STR
- * @access Public
- * @return void
- */
- function My_urlencode ($STR) {
- Return Str_replace ('%7e ', ' ~ ', Rawurlencode ($STR));
- }
- /**
- * My_build_query
- *
- * @param mixed $params
- * @access Public
- * @return void
- */
- function My_build_query ($parameters) {
- $params = Array ();
- foreach ($parameters as $var = = $value) {
- if (Is_array ($value)) {
- foreach ($value as $v) {
- $params [] = $var. ' = '. My_urlencode ($v);
- }
- } else {
- $params [] = $var. ' = '. My_urlencode ($value);
- }
- }
- Sort ($params, sort_string);
- Return implode (' & ', $params);
- }
- /**
- * My_headers
- *
- * @param mixed $headers
- * @access Public
- * @return void
- */
- function My_headers () {
- $date = Gmdate (' d, D M Y h:i:s e ');
- $sig = Base64_encode (Hash_hmac (' sha256 ', $date, Ses_secret, true));
- $headers = Array ();
- $headers [] = ' Date: '. $date;
- $headers [] = ' Host: '. Ses_host;
- $auth = ' Aws3-https awsaccesskeyid= '. Ses_key;
- $auth. = ', algorithm=hmacsha256,signature= '. $sig;
- $headers [] = ' x-amzn-authorization: '. $auth;
- $headers [] = ' content-type:application/x-www-form-urlencoded ';
- return $headers;
- }
- /**
- * Parse_data
- *
- * @param mixed $data
- * @access Public
- * @return void
- */
- Function Parse_data (& $data) {
- My_parse_str ($data, $params);
- if (!empty ($params)) {
- $redis = new Redis ();
- $redis->connect (Redis_host, Redis_port);
- Multiple Send addresses
- if (Isset ($params [' destination.toaddresses.member.2 '])) {
- $address = Array ();
- $mKey = Uniqid ();
- $i = 2;
- while (Isset ($params [' Destination.ToAddresses.member. $i])} {
- $aKey = Uniqid ();
- $key = ' Destination.ToAddresses.member. ' $i;
- $address [$aKey] = $params [$key];
- Unset ($params [$key]);
- $i + +;
- }
- $data = My_build_query ($params);
- unset ($params [' Destination.toaddresses.member.1 ']);
- $redis->set (' m: '. $mKey, My_build_query ($params));
- foreach ($address as $k = = $a) {
- $redis->hset (' A: '. $mKey, $k, $a);
- $redis->lpush (' Mail ', $k. '|' . $mKey);
- }
- }
- }
- }
Copy CodeYou can see that the Parse_data function starts with a second recipient, assembles them into a single message, and puts them into the Redis queue for other independent processes to read and send. Why not start with the first recipient? Because to be compatible with the original protocol, the client sends a mail request you always have to return a thing, I am too lazy to forge, so it's the first recipient of the email request is sent directly, and did not enter the queue, so I can get a real SES server receipts returned to the client, The client code can handle this return without any modification. What if the SES mail is signed? Yes, all SES messages need to be signed. So after you unpack, the mail data changes, so the signature must change. The My_build_query function is doing this, and it re-signs the request parameters. The following is the last component of this proxy system, the mail send queue implementation, it is also a PHP file, you can use the nohup PHP command in the background to launch several PHP processes in the backend to implement the concurrent mail sending, based on your quota size. Its structure is also very simple, is to read the message in the queue and send the request with Curl
-
- Include __dir__. '/includes.php ';
- $redis = new Redis ();
- $redis->connect (Redis_host, Redis_port);
- do {
- $pop = $redis->brpop (' Mail ', 10);
- if (empty ($pop)) {
- Continue
- }
- List ($k, $id) = $pop;
- List ($aKey, $mKey) = Explode (' | ', $id);
- $address = $redis->hget (' A: '. $mKey, $aKey);
- if (empty ($address)) {
- Continue
- }
- $data = $redis->get (' m: '. $mKey);
- if (empty ($data)) {
- Continue
- }
- My_parse_str ($data, $params);
- $params [' destination.toaddresses.member.1 '] = $address;
- $data = My_build_query ($params);
- $headers = My_headers ();
- $url = ' https://'. Ses_host. '/';
- $ch = Curl_init ();
- curl_setopt ($ch, curlopt_useragent, ' simpleemailservice/php ');
- curl_setopt ($ch, Curlopt_ssl_verifyhost, false);
- curl_setopt ($ch, Curlopt_ssl_verifypeer, false);
- curl_setopt ($ch, Curlopt_customrequest, ' POST ');
- curl_setopt ($ch, Curlopt_returntransfer, true);
- curl_setopt ($ch, Curlopt_postfields, $data);
- curl_setopt ($ch, Curlopt_httpheader, $headers);
- curl_setopt ($ch, Curlopt_url, $url);
- curl_setopt ($ch, Curlopt_timeout, 10);
- Curl_exec ($ch);
- Curl_close ($ch);
- Unset ($ch);
- Unset ($data);
- } while (true);
Copy CodeThis is the whole idea that I write the SES mail proxy server, welcome everybody to discuss together. |