Implementing a proxy server for Amazon ses with PHP

Source: Internet
Author: User

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 function

The 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

  1. Include __dir__. '/includes.php ';
  2. Here are some of the more important headers, others don't need attention
  3. $headers = Array (
  4. ' Date: '. Get_header (' Date '),
  5. ' Host: '. Ses_host,
  6. ' X-amzn-authorization: '. Get_header (' x-amzn-authorization ')
  7. );
  8. Then assemble the URL again to request this positive SES server
  9. $url = ' https://'. Ses_host. '/'
  10. . (Empty ($_server[' query_string ')? '' : '?' . $_server[' query_string ');
  11. $ch = Curl_init ();
  12. curl_setopt ($ch, curlopt_useragent, ' simpleemailservice/php ');
  13. curl_setopt ($ch, Curlopt_ssl_verifyhost, 1);
  14. curl_setopt ($ch, Curlopt_ssl_verifypeer, 1);
  15. Need to deal with the ' POST ' and ' DELETE ' method, the ' GET ' method is much more than I can achieve
  16. In fact, there are some ways to get the current information, which you can see directly in the background.
  17. Switch ($_server[' Request_method ')} {
  18. Case ' GET ':
  19. Break
  20. Case ' POST ':
  21. Global $HTTP _raw_post_data;
  22. $data = Empty ($HTTP _raw_post_data)? file_get_contents (' Php://input ')
  23. : $HTTP _raw_post_data;
  24. $headers [] = ' content-type:application/x-www-form-urlencoded ';
  25. Parse_data ($data);
  26. curl_setopt ($ch, Curlopt_customrequest, ' POST ');
  27. curl_setopt ($ch, Curlopt_postfields, $data);
  28. Break
  29. Case ' DELETE ':
  30. curl_setopt ($ch, Curlopt_customrequest, ' DELETE ');
  31. Break
  32. Default
  33. Break
  34. }
  35. curl_setopt ($ch, Curlopt_httpheader, $headers);
  36. curl_setopt ($ch, Curlopt_header, false);
  37. curl_setopt ($ch, Curlopt_url, $url);
  38. curl_setopt ($ch, Curlopt_returntransfer, true);
  39. curl_setopt ($ch, curlopt_followlocation, true);
  40. $response = curl_exec ($ch);
  41. $content _type = Curl_getinfo ($ch, Curlinfo_content_type);
  42. $status = Curl_getinfo ($ch, Curlinfo_http_code);
  43. Curl_close ($ch);
  44. Header (' Content-type: '. $content _type, True, $status);
  45. Echo $response;
Copy Code

This 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.

  1. Define (' Redis_host ', ' 127.0.0.1 ');
  2. Define (' Redis_port ', 6379);
  3. Define (' Ses_host ', ' email.us-east-1.amazonaws.com ');
  4. Define (' Ses_key ', ');
  5. Define (' Ses_secret ', ');
  6. /**
  7. * Get_header
  8. *
  9. * @param mixed $name
  10. * @access Public
  11. * @return void
  12. */
  13. function Get_header ($name) {
  14. $name = ' http_ '. Strtoupper (Str_replace ('-', ' _ ', $name));
  15. return Isset ($_server[$name])? $_server[$name]: ";
  16. }
  17. /**
  18. * MY_PARSE_STR
  19. *
  20. * @param mixed $query
  21. * @param mixed $params
  22. * @access Public
  23. * @return void
  24. */
  25. function My_parse_str ($query, & $params) {
  26. if (empty ($query)) {
  27. Return
  28. }
  29. $decode = function ($str) {
  30. Return Rawurldecode (Str_replace (' ~ ', '%7e ', $str));
  31. };
  32. $data = Explode (' & ', $query);
  33. $params = Array ();
  34. foreach ($data as $value) {
  35. List ($key, $val) = explode (' = ', $value, 2);
  36. if (Isset ($params [$key])) {
  37. if (!is_array ($params [$key])) {
  38. $params [$key] = Array ($params [$key]);
  39. }
  40. $params [$key] = $val;
  41. } else {
  42. $params [$key] = $decode ($val);
  43. }
  44. }
  45. }
  46. /**
  47. * My_urlencode
  48. *
  49. * @param mixed $STR
  50. * @access Public
  51. * @return void
  52. */
  53. function My_urlencode ($STR) {
  54. Return Str_replace ('%7e ', ' ~ ', Rawurlencode ($STR));
  55. }
  56. /**
  57. * My_build_query
  58. *
  59. * @param mixed $params
  60. * @access Public
  61. * @return void
  62. */
  63. function My_build_query ($parameters) {
  64. $params = Array ();
  65. foreach ($parameters as $var = = $value) {
  66. if (Is_array ($value)) {
  67. foreach ($value as $v) {
  68. $params [] = $var. ' = '. My_urlencode ($v);
  69. }
  70. } else {
  71. $params [] = $var. ' = '. My_urlencode ($value);
  72. }
  73. }
  74. Sort ($params, sort_string);
  75. Return implode (' & ', $params);
  76. }
  77. /**
  78. * My_headers
  79. *
  80. * @param mixed $headers
  81. * @access Public
  82. * @return void
  83. */
  84. function My_headers () {
  85. $date = Gmdate (' d, D M Y h:i:s e ');
  86. $sig = Base64_encode (Hash_hmac (' sha256 ', $date, Ses_secret, true));
  87. $headers = Array ();
  88. $headers [] = ' Date: '. $date;
  89. $headers [] = ' Host: '. Ses_host;
  90. $auth = ' Aws3-https awsaccesskeyid= '. Ses_key;
  91. $auth. = ', algorithm=hmacsha256,signature= '. $sig;
  92. $headers [] = ' x-amzn-authorization: '. $auth;
  93. $headers [] = ' content-type:application/x-www-form-urlencoded ';
  94. return $headers;
  95. }
  96. /**
  97. * Parse_data
  98. *
  99. * @param mixed $data
  100. * @access Public
  101. * @return void
  102. */
  103. Function Parse_data (& $data) {
  104. My_parse_str ($data, $params);
  105. if (!empty ($params)) {
  106. $redis = new Redis ();
  107. $redis->connect (Redis_host, Redis_port);
  108. Multiple Send addresses
  109. if (Isset ($params [' destination.toaddresses.member.2 '])) {
  110. $address = Array ();
  111. $mKey = Uniqid ();
  112. $i = 2;
  113. while (Isset ($params [' Destination.ToAddresses.member. $i])} {
  114. $aKey = Uniqid ();
  115. $key = ' Destination.ToAddresses.member. ' $i;
  116. $address [$aKey] = $params [$key];
  117. Unset ($params [$key]);
  118. $i + +;
  119. }
  120. $data = My_build_query ($params);
  121. unset ($params [' Destination.toaddresses.member.1 ']);
  122. $redis->set (' m: '. $mKey, My_build_query ($params));
  123. foreach ($address as $k = = $a) {
  124. $redis->hset (' A: '. $mKey, $k, $a);
  125. $redis->lpush (' Mail ', $k. '|' . $mKey);
  126. }
  127. }
  128. }
  129. }
Copy Code

You 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

  1. Include __dir__. '/includes.php ';
  2. $redis = new Redis ();
  3. $redis->connect (Redis_host, Redis_port);
  4. do {
  5. $pop = $redis->brpop (' Mail ', 10);
  6. if (empty ($pop)) {
  7. Continue
  8. }
  9. List ($k, $id) = $pop;
  10. List ($aKey, $mKey) = Explode (' | ', $id);
  11. $address = $redis->hget (' A: '. $mKey, $aKey);
  12. if (empty ($address)) {
  13. Continue
  14. }
  15. $data = $redis->get (' m: '. $mKey);
  16. if (empty ($data)) {
  17. Continue
  18. }
  19. My_parse_str ($data, $params);
  20. $params [' destination.toaddresses.member.1 '] = $address;
  21. $data = My_build_query ($params);
  22. $headers = My_headers ();
  23. $url = ' https://'. Ses_host. '/';
  24. $ch = Curl_init ();
  25. curl_setopt ($ch, curlopt_useragent, ' simpleemailservice/php ');
  26. curl_setopt ($ch, Curlopt_ssl_verifyhost, false);
  27. curl_setopt ($ch, Curlopt_ssl_verifypeer, false);
  28. curl_setopt ($ch, Curlopt_customrequest, ' POST ');
  29. curl_setopt ($ch, Curlopt_returntransfer, true);
  30. curl_setopt ($ch, Curlopt_postfields, $data);
  31. curl_setopt ($ch, Curlopt_httpheader, $headers);
  32. curl_setopt ($ch, Curlopt_url, $url);
  33. curl_setopt ($ch, Curlopt_timeout, 10);
  34. Curl_exec ($ch);
  35. Curl_close ($ch);
  36. Unset ($ch);
  37. Unset ($data);
  38. } while (true);
Copy Code

This is the whole idea that I write the SES mail proxy server, welcome everybody to discuss together.

Proxy Server, PHP, Amazon
  • Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.