Integration of Amazon SES SDK

I've built a simple plugin to integrate Amazon SES SDK, but it does not work.

example.com/members/mod/amazon/start.php

<?php


elgg_register_event_handler('init', 'system', 'amazon_ses_init');


function amazon_ses_init() {


   elgg_register_plugin_hook_handler('email', 'system', function($hook, $type, $returnvalue, $params)     {

        if ($returnvalue === true) {

            return;

        }


    $to_address = '';

$contact = $returnvalue['to'];

$containsName = preg_match('/<(.*)>/', $contact, $matches) == 1;

if ($containsName) {

$to_address = $matches[1];

} else {

$to_address = $contact;

}

if (!$to_address) {

return $returnvalue;

}

$users = get_user_by_email($to_address);

$recipient = $users[0];


if (!($recipient instanceof ElggUser)) {

return $returnvalue;

}

$message = $returnvalue['body'];

    $subject = $returnvalue['subject'];

    $from = $returnvalue['from'];

   

    /** ==========AMAZON SES (comment/uncomment) ====================== */

    include_once(dirname(__FILE__) . '/functions/amazonSes.php');

    mailAmazonSES($to_address,$from,$subject,$message);


    /** ==========mail PHP  (uncomment/comment)====================== */

    /*$headers = "From: $from";

    mail($to_address,$subject,$message,$headers);*/


   return true;

  });

}
 
<?php

use Aws\Ses\SesClient;

use Aws\Ses\Exception\SesException;


function mailAmazonSES($to,$from,$subject,$textMSG) {

    include_once './Amazon-AWS-SDK/aws-autoloader.php';


    $client = SesClient::factory(array(

        'version'=> 'latest',     

        'region' => 'us-east-1'

    ));

    $charset = 'UTF-8';

    try {

        $result = $client->sendEmail([

        'Destination' => [

            'ToAddresses' => [

                $to,

            ],

        ],

        'Message' => [

            'Body' => [

'Text' => [

                    'Charset' => $charset,

                    'Data' => $textMSG,

                ],

            ],

            'Subject' => [

                'Charset' => $charset,

                'Data' => $subject,

            ],

        ],

    'Source' => $from, 

  ]);

     $messageId = $result->get('MessageId');

     //echo("Email sent! Message ID: $messageId"."\n");


    } catch (SesException $error) {

        //echo("The email was not sent. Error message: ".$error->getAwsErrorMessage()."\n");

    }

}
 
<?php
include_once(dirname(__FILE__) . '/functions/amazonSes.php');
    $from = 'noreply@example.com';
    $to_address = 'zelinkovsky@gmail.com';
    $subject = "My Test Subject";
    $textMSG = "Dear Text Friend,\r\nThis email was send with Amazon SES using the AWS SDK for PHP";
   
     mailAmazonSES($to_address,$from,$subject,$textMSG);
 ?>

It works well when I try to call the SDK directly through example.com/members/mod/amazon/amazon-test.php

When I try the start.php with mail php (see at the bottom of start.php) it works well.

However, it does not work when I try start.php with the function mailAmazonSES 

Please advise

Thank you

  • Please create a gist on github, we can't read this

  • Thank you Ismayil for the prompt replay

    Here it is again again:

    Based on your advice I've built a simple plugin to integrate Amazon SES SDK,

    I've uploaded 3 files to the gist: https://gist.github.com/reuvenz/34fda60ade1eb2cf88a7d31ce6e0ba1c

    1) start.php - at: example.com/members/mod/amazon/start.php

    2) amazonSes.php - a function at: example.com/members/mod/amazon/functions/amazonSes.php

    3) amazon-test.php - a test file at: example.com/members/mod/amazon/amazon-test.php

    When I go directly to the test file example.com/members/mod/amazon/amazon-test.php Amazon SDK works well.

    When I test the start.php with mail php (see the comment / Uncomment at the bottom of the script), it works well.

    However when I test start.php with Amazon SDK, i.e. with mailAmazonSES function, it does not work.

    Than you for your help

    Reuven

  • How do you define "it doesn't work"? Does it not send the email? Or does it throw and exception.
    I am not sure if including a file within a function does what you expect it to do. It probably produces a scoped subfunction. Try using require outside of the function body instead.

  • I've already tried to include it above elgg_register_event_handler('init', 'system', 'amazon_ses_init') and also between function amazon_ses_init() and elgg_register_plugin_hook_handler('email', 'system', function($hook, $type, $returnvalue, $params), but it does not work.

    "does not work" = does not send emails.

    I've no idea what to try next.

     

  • When you directly call amazon-test.php you would call mailAmazonSES() with some test paramters (values for $to, $from etc.), right? Have you tested with the same values that would be used when mailAmazonSES() is called from the plugin hook handler callback? For example, is the $from mail address the same?

    Does mailAmazonSES() gets even executed when called from the callback function and not called directly? Does the code execution inside mailAmazonSES() gets beyond the include_once line?

    You could add a elgg_dump() for example after the include_once line in amazon-test.php that would output the values of function parameters ($to, $from, etc.) into the server error log to see if these values are what you expect (also regarding variable type) and to see if there's a difference between calling mailAmazonSES() directly and the function getting called by the plugin hook handler callback.

  • Thank you iionly. I've done a few more tests and have answers to your questions. However,  I think we will be more efficient if I give you FTP access and make you admin.

    I'm only learning Elgg now and the Elgg installation is temporary.

    If you agree please let me know.

    Thank you

  • https://gist.github.com/reuvenz/9de5bdbfcd1956eab5a7a404300b030f

    To test the include path I have created a new test-function.php in the same folder (members/mod/amazon_ses/functions) of amazonSes.php. This function returns a test string which is added to the original message, both in  amazon-test.php and start.php. It works fine.

    Also,  amazonSes.php is now returning a string indicating success or failure, which is added to the original message.

    Now amazon-test.php and start.php are sending emails twice, through amazon and mail.

    amazon-test.php sends the two emails successfully. start.php does not send any email. Only when I comment amazon it send the emails through mail.

    ​It seems, that amazon blocks somehow the progress of the software.

    And... I use the same from and to addresses.

     

     

  • 1. You need to call elgg_send_mail() (or trigger any other sort of email notification) for the hook to have any effect

    2. I am not sure your regex is bullet proof. Make sure that the $to value is correct. Don't test if there is an ElggUser, because the emails can be sent by the system to non-users. If you need to extract an email from the To address, use helper class in core (\Elgg\Mail\Address I think)

  • Thank you Ismayil.

    As far as I understand you are talking about two different things:

    1. You need to call elgg_send_mail() (or trigger any other sort of email notification) for the hook to have any effect

    I'm not sure I understand this point, since the hook is working fine with mail(). The same hook does not send emails when I comment mail() and un-comment Amazon SES. Could you please give me an example.

    2. I am not sure your regex is bullet proof. Make sure that the $to value is correct. Don't test if there is an ElggUser, because the emails can be sent by the system to non-users. If you need to extract an email from the To address, use helper class in core (\Elgg\Mail\Address I think)

    You are right - the system sends email to new subscribers with a link to confirm their email. Here also I appreciate an example (sorry, but I'm new to Elgg and need more help).

    Thank you

  • As I understand you want mails to be sent by AWS instead of Elgg sending mails on its own. Right? So, I think it's alright to use the 'email', 'system' hook and dealing in the callback with the mail sending. This hook is triggered by elgg_send_email() and by returning true in your callback function it tells Elgg that it does not have to deal with the mail sending anymore for this message.

    The regex stuff seems also alright to me (I had recently worked with the same code in my email_postscript plugin). I wouldn't make use of the Elgg helper class here because it works with some ZendMailer type object and it seems not possible to change the address variable value back into a string to use it then further.

    I don't know if the following code works. I just thought it better to remove all the test code for the alternative methods - after all you already found out that it works. Also it seemed less complex to just include the whole code into the callback directly instead of including it. I've added some debug output that might help for testing to know what paramter values are used when sending the mail. With debugging enabled in the advanced site settings ("Warnings and errors") you should see the output in the error log.

    Maybe the code does not work (I can't test it without AWS) but you might want to continue with it and rather fix the errors still in it instead of adding more and more code that only proofs that other methods work.

    <?php
    
    // Not sure about where you have the ASW libs and classes and if there are correctly loaded with the following lines
    require_once(dirname(__FILE__) . '/functions/Amazon-AWS-SDK/aws-autoloader.php');
    use Aws\Ses\SesClient;
    use Aws\Ses\Exception\SesException;
    
    elgg_register_event_handler('init', 'system', 'amazon_ses_init');
    
    function amazon_ses_init() {
       elgg_register_plugin_hook_handler('email', 'system', 'amazon_ses_send_email');
    }
    
    function amazon_ses_send_email($hook, $type, $returnvalue, $params) {
        if (!is_array($returnvalue) || !is_array($returnvalue['params'])) {
            // another hook handler returned a non-array, let's not override it
            return;
        }
    
        $to_address = '';
        $contact = $returnvalue['to'];
        $containsName = preg_match('/<(.*)>/', $contact, $matches) == 1;
        if ($containsName) {
            $to_address = $matches[1];
        } else {
            $to_address = $contact;
        }
    
        if (!$to_address) {
            return $returnvalue;
        }
    
        $users = get_user_by_email($to_address);
        $recipient = $users[0];
    
        if (!($recipient instanceof ElggUser)) {
            return $returnvalue;
        }
    
        $message = $returnvalue['body'];
        $subject = $returnvalue['subject'];
        $from = $returnvalue['from'];
    
        $client = SesClient::factory([
            'version'=> 'latest',
            'region' => 'us-east-1'
        ]);
        $charset = 'UTF-8';
    
    // just for debugging: do we have the expected parameters for the email at this point?
    elgg_dump('To: '.$to_address, false, 'NOTICE');
    elgg_dump('From: '.$from, false, 'NOTICE');
    elgg_dump('Subject: '.$subject, false, 'NOTICE');
    elgg_dump('Body: '.$message, false, 'NOTICE');
    
        $result = $client->sendEmail([
            'Destination' => [
                'ToAddresses' => [
                    $to_address,
                ],
            ],
            'Message' => [
                'Body' => [
                    'Text' => [
                        'Charset' => $charset,
                        'Data' => $message,
                    ],
                ],
                'Subject' => [
                    'Charset' => $charset,
                    'Data' => $subject,
                ],
            ],
            'Source' => $from,
        ]);
    
        // Not sure what gets returned in case the email was not sent... assuming false or empty string...
        $messageId = $result->get('MessageId');
    
    // Log messageId
    elgg_dump('MessageId: '.$messageId, false, 'NOTICE');
    
        if ($messageId) {
            return true;
        }
    
        return $returnvalue;
    }