Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #34: Added support for attachments #35

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

faishal
Copy link

@faishal faishal commented Feb 23, 2018

  • Created multipart email for mails with attachments.
  • Added param in wp-cli commad.
  • Updated readme with example command.

Copy link
Member

@rmccue rmccue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also want review from @joehoyle on this.

*
* @return string
*/
private function get_raw_message( $to, $subject, $message, $headers = array(), $attachments = array() ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be protected rather than private

$reply_to = array_merge( (array) $reply_to, explode( ',', $content ) );
break;
default:
$raw_message_header .= $name . ': ' . $content . "\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should $content be newline-escaped to avoid header overflow issues?

$raw_message .= 'MIME-Version: 1.0' . "\n";
$raw_message .= 'Content-type: Multipart/Mixed; boundary="' . $boundary . '"' . "\n";
$raw_message .= "\n--{$boundary}\n";
$raw_message .= 'Content-type: Multipart/Alternative; boundary="alt-' . $boundary . '"' . "\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should be Content-Type with the actual type lowercase?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into RFC for that, In starting it showing Content-Type but in the example, it was showing Content-type, so I tested both and working fine.
Ref: https://www.w3.org/Protocols/rfc1341/rfc1341.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, headers are theoretically case-insensitive (likewise in HTTP), but in practice, it's best to use the exact case.

$custom_from = sprintf( '%s <%s>', apply_filters( 'wp_mail_from_name', get_bloginfo( 'name' ) ), apply_filters( 'wp_mail_from', $from_email ) );
}

$boundary = uniqid( rand(), true );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wp_rand() would be better than uniqid( rand() ) I think, and I'd probably add a prefix as well:

$boundary = 'aws-ses-wp-mail-' . wp_rand();


$raw_message .= 'MIME-Version: 1.0' . "\n";
$raw_message .= 'Content-type: Multipart/Mixed; boundary="' . $boundary . '"' . "\n";
$raw_message .= "\n--{$boundary}\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably use sprintf() for these instead, I think it's a bit more readable.

$raw_message .= "\n--alt-{$boundary}--\n";

foreach ( $attachments as $attachment ) {
if ( ! @is_file( $attachment ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to check this and avoid the error suppression operator?

}

$raw_message .= "\n--{$boundary}\n";
$raw_message .= 'Content-Type: ' . $file_type['type'] . '; name="' . $filename . '"' . "\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use sprintf() for this.

@joehoyle
Copy link
Member

I thing the biggest problem here is that working with attachments means different filters and way of sending the message, which is going to be annoying to work with. As a developer I can't rely on aws_ses_wp_mail_ses_send_message_args in case email would happen to have an attachment. That's probably not such a big issue if we have "higher level" hooks for most configuration. I know I've had to use this catch-all hook for things before as a suitable WordPress hook for email modification hasn't existed.

We could also consider only ever sending raw, so there's only one method to deal with. I don't see much value in keeping around SendMessage if we have to have a fully reliable SendRawMessage implementation. Having a branch is undoubtedly going to cause some bugs. However, SendMessage is presumably easier to implement and less error prone, so we need to weigh up the benefit of that versus the pretty rarely used sending of attachments.

If we push ahead I think get_raw_message needs to be broken up to have the parsing be in their own functions to actually be able to read this code without getting lost. This like concat of the strings with \n are also pretty gnarly to read, we should be able to make this is a little clearer with some imploding etc.

@faishal
Copy link
Author

faishal commented Feb 27, 2018

@joehoyle That makes sense, After reading your comment I dig into what we can do to make it simple.

How about using PHPMailer class of WordPress and build raw email?

I looked into the wp_mail function for phpmailer usage and able to generate a raw email using it :
https://gist.github.com/faishal/5d6d1c2a0f03d9a7534f9e836bcd6c34

We can also create a custom class to do that but I feel like phpMailer is providing all encoding supports.

@joehoyle
Copy link
Member

joehoyle commented Mar 3, 2018

Interesting, I think using PHPMailer might be handy. In that case, do we even need to hook wp_mail, can't we just capture the PHPMailer getSentMIMEMessage pre-send?

@faishal
Copy link
Author

faishal commented Mar 5, 2018

Do you mean removing wp_mail pluggable method from plugin?

In that case it will not require to build the mail, we just have to send it via aws ses api.
Unfortunately, There isn't any hook/filter in class-phpmailer.php, but there is one way I can think of.

PHPMailer::postSend calls Method based on PHPMailer::Mailer value, so if we set PHPMailer::Mailer value to awsses then it will call awssesSend method.

If we don't want to repeat what wp_mail is doing and just want to hook custom send method then we can do following.

  1. Define SES_PHPMailer from base class PHPMailer with custom awssesSend method.
  2. Hook into wp_mail filter and initiate custom SES_PHPMailer class object and assign it to global $phpmailer only if we have the valid AWS constants defined.
  3. Hook into phpmailer_init and set PHPMailer::Mailer value to awsses so that it will call awssesSend method.
  4. in awssesSend we will get raw mail body by calling getSentMIMEMessage method, We just have to initiate aws client and send mail using sendRawEmail method of aws and throw an appropriate phpmailerException exceptions if we catch any.

@joehoyle
Copy link
Member

joehoyle commented Mar 8, 2018

Cool yeah that's what I was getting at. I think this approach is certainly interesting. I'm not 100% whether to proceed - what would be your suggested route?

@faishal
Copy link
Author

faishal commented Mar 12, 2018

@joehoyle We can set milestone 0.2.0 which will include the above-mentioned refactoring and close this PR. We can test this with other major plugins to verify its working as expected.

@faishal
Copy link
Author

faishal commented Apr 5, 2018

@joehoyle - I created PR #37 with above suggested changes

}
$raw_message .= sprintf( "\n--alt-%s--\n", $boundary );

foreach ( $attachments as $attachment ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@faishal - wp_mail $attachments param also accepts a single string file location as a valid param. Currently, passing a single string results in attachments not being sent here.

We could follow cores approach and cast as follows (before entering the loop here)

    if ( ! is_array( $attachments ) ) {
        $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
    }

@roborourke
Copy link
Contributor

Is this already in use? I noticed we have a 2.0.0-alpha tag with this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants