com.amazonaws.services.simpleemail.AWSJavaMailTransport Maven / Gradle / Ivy
Show all versions of aws-java-sdk-ses Show documentation
/*
* Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.services.simpleemail;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.event.TransportEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleemail.model.RawMessage;
import com.amazonaws.services.simpleemail.model.SendRawEmailRequest;
import com.amazonaws.services.simpleemail.model.SendRawEmailResult;
import com.amazonaws.util.VersionInfoUtils;
/**
* A transport implementation using Amazon Web Service's E-mail Service. For
* JavaMail purposes this transport implementation uses the "aws" protocol. In
* order to send messages through the E-mail Service your AWS Credentials
* (http://aws.amazon.com/security-credentials) need to be either in the
* JavaMail Session's Properties (mail.aws.user and mail.aws.password), passed
* into the connect() method, or set in the Session's setPasswordAuthentication
* method. Parameters passed into the connect method as well as
* PasswordAuthentication information supersedes the properties field for a
* particular session. When connecting your AWS Access Key is your username and
* your AWS Secret Key is your password.
*
* This transport implementation only accepts MIME encoded messages (see
* MimeMessage class) and RFC822 E-mail addresses (see InternetAddress class).
*/
public class AWSJavaMailTransport extends Transport {
public static final String AWS_EMAIL_SERVICE_ENDPOINT_PROPERTY = "mail.aws.host";
public static final String AWS_SECRET_KEY_PROPERTY = "mail.aws.password";
public static final String AWS_ACCESS_KEY_PROPERTY = "mail.aws.user";
private AmazonSimpleEmailServiceClient emailService;
private final String accessKey;
private final String secretKey;
private final String httpsEndpoint;
private String lastMessageId;
public AWSJavaMailTransport(Session session, URLName urlname) {
super(session, urlname);
this.accessKey = session.getProperty(AWS_ACCESS_KEY_PROPERTY);
this.secretKey = session.getProperty(AWS_SECRET_KEY_PROPERTY);
this.httpsEndpoint = session.getProperty(AWS_EMAIL_SERVICE_ENDPOINT_PROPERTY);
}
/**
* Sends a MIME message through Amazon's E-mail Service with the specified
* recipients. Addresses that are passed into this method are merged with
* the ones already embedded in the message (duplicates are removed).
*
* @param msg
* A Mime type e-mail message to be sent
* @param addresses
* Additional e-mail addresses (RFC-822) to be included in the
* message
*/
@Override
public void sendMessage(Message msg, Address[] addresses)
throws MessagingException, SendFailedException {
checkConnection();
checkMessage(msg);
checkAddresses(msg, addresses);
collateRecipients(msg, addresses);
SendRawEmailRequest req = prepareEmail(msg);
sendEmail(msg, req);
}
/**
* Asserts a valid connection to the email service.
*/
private void checkConnection() {
if (emailService == null || !super.isConnected()) {
throw new IllegalStateException("Not connected");
}
}
/**
* Checks that the message can be sent using AWS Simple E-mail Service.
*/
private void checkMessage(Message msg) throws MessagingException {
if (msg == null) {
throw new MessagingException("Message is null");
}
if (!(msg instanceof MimeMessage)) {
throw new MessagingException(
"AWS Mail Service can only send MimeMessages");
}
}
/**
* Checks to ensure at least one recipient is present (either in the Message
* or Address[]) and all addresses that are passed in using the Address
* array are of type InternetAddress.
*/
private void checkAddresses(Message m, Address[] addresses)
throws MessagingException, SendFailedException {
if ( isNullOrEmpty((Object[]) addresses)
&& isNullOrEmpty((Object[]) m.getRecipients(Message.RecipientType.TO))
&& isNullOrEmpty((Object[]) m.getRecipients(Message.RecipientType.CC))
&& isNullOrEmpty((Object[]) m.getRecipients(Message.RecipientType.BCC)) ) {
throw new SendFailedException("No recipient addresses");
}
// Make sure all addresses are internet addresses
Set
invalid = new HashSet();
for ( Address[] recipients : new Address[][] {
m.getRecipients(Message.RecipientType.TO),
m.getRecipients(Message.RecipientType.CC),
m.getRecipients(Message.RecipientType.BCC),
addresses } ) {
if ( !isNullOrEmpty(recipients) ) {
for ( Address a : recipients ) {
if ( !(a instanceof InternetAddress) ) {
invalid.add(a);
}
}
}
}
if ( !invalid.isEmpty() ) {
Address[] sent = new Address[0];
Address[] unsent = new Address[0];
super.notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent,
invalid.toArray(new Address[invalid.size()]), m);
throw new SendFailedException("AWS Mail Service can only send to InternetAddresses");
}
}
/**
* Collates any addresses into the message object. All addresses in the Address array
* become of type TO unless they already exist in the Message header. If
* they are in the Message header they will stay of the same type. Any
* duplicate addresses are removed. Type BCC and then CC takes precedence
* over TO when duplicates exist. If any address is invalid an exception is
* thrown.
*/
private void collateRecipients(Message m, Address[] addresses) throws MessagingException {
if ( !isNullOrEmpty(addresses) ) {
Hashtable addressTable = new Hashtable();
for ( Address a : addresses ) {
addressTable.put(a, Message.RecipientType.TO);
}
if ( !isNullOrEmpty(m.getRecipients(Message.RecipientType.TO)) ) {
for ( Address a : m.getRecipients(Message.RecipientType.TO) ) {
addressTable.put(a, Message.RecipientType.TO);
}
}
if ( !isNullOrEmpty(m.getRecipients(Message.RecipientType.CC)) ) {
for ( Address a : m.getRecipients(Message.RecipientType.CC) ) {
addressTable.put(a, Message.RecipientType.CC);
}
}
if ( !isNullOrEmpty(m.getRecipients(Message.RecipientType.BCC)) ) {
for ( Address a : m.getRecipients(Message.RecipientType.BCC) ) {
addressTable.put(a, Message.RecipientType.BCC);
}
}
// Clear the original recipients for collation
m.setRecipients(Message.RecipientType.TO, new Address[0]);
m.setRecipients(Message.RecipientType.CC, new Address[0]);
m.setRecipients(Message.RecipientType.BCC, new Address[0]);
Iterator aIter = addressTable.keySet().iterator();
while ( aIter.hasNext() ) {
Address a = aIter.next();
m.addRecipient(addressTable.get(a), a);
}
// Simple E-mail needs at least one TO address, so add one if there isn't one
if ( m.getRecipients(Message.RecipientType.TO) == null ||
m.getRecipients(Message.RecipientType.TO).length == 0 ) {
m.setRecipient(Message.RecipientType.TO, addressTable.keySet().iterator().next());
}
}
}
/**
* Prepares the email to be sent using the JavaMail service. Wraps up the
* message into a RawEmailRequest object to be processed by AWS's
* sendRawEmail().
*
* @param m
* A JavaMail message to be converted to a request
* @return A Raw Email Request for AWS E-mail Service
*/
private SendRawEmailRequest prepareEmail(Message m)
throws MessagingException {
try {
OutputStream byteOutput = new ByteArrayOutputStream();
m.writeTo(byteOutput);
SendRawEmailRequest req = new SendRawEmailRequest();
byte[] messageByteArray = ((ByteArrayOutputStream) byteOutput)
.toByteArray();
RawMessage message = new RawMessage();
message.setData(ByteBuffer.wrap(messageByteArray));
req.setRawMessage(message);
return req;
} catch (Exception e) {
Address[] sent = new Address[0];
Address[] unsent = new Address[0];
Address[] invalid = m.getAllRecipients();
super.notifyTransportListeners(
TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent,
invalid, m);
throw new MessagingException("Unable to write message: "
+ m.toString(), e);
}
}
/**
* Sends an email using AWS E-mail Service and notifies listeners
*
* @param m
* Message used to notify users
* @param req
* Raw email to be sent
*/
private void sendEmail(Message m, SendRawEmailRequest req)
throws SendFailedException, MessagingException {
Address[] sent = null;
Address[] unsent = null;
Address[] invalid = null;
try {
appendUserAgent(req, USER_AGENT);
SendRawEmailResult resp = this.emailService.sendRawEmail(req);
lastMessageId = resp.getMessageId();
sent = m.getAllRecipients();
unsent = new Address[0];
invalid = new Address[0];
super.notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
sent, unsent, invalid, m);
} catch (Exception e) {
sent = new Address[0];
unsent = m.getAllRecipients();
invalid = new Address[0];
super.notifyTransportListeners(
TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent,
invalid, m);
throw new SendFailedException("Unable to send email", e, sent,
unsent, invalid);
}
}
/**
* Sets up a new AmazonSimpleEmailServiceClient. This method is typically called
* indirectly from the connect() method and should only be called on
* instantiation or to reopen after a close(). If a non-null or empty User
* and Password passed in session properties are overridden while user
* remains connected (mail.aws.user and mail.aws.password). The default
* https endpoint is specified by the mail client; however, it can be
* overridden by either passing in a value or setting mail.aws.host. Like
* the user and password, the variable that is passed in takes preference
* over the properties file.
*
* @param host
* Optional - host specifies the AWS E-mail endpoint
* @param awsAccessKey
* Optional - AWS Access Key (otherwise must specify through
* properties file)
* @param awsSecretKey
* Optional - AWS Secret key (otherwise must specify through
* properties file)
* @return Returns true if non-empty credentials are given
*/
@Override
protected boolean protocolConnect(String host, int port, String awsAccessKey,
String awsSecretKey) {
if (isConnected())
throw new IllegalStateException("Already connected");
if (isNullOrEmpty(awsAccessKey) || isNullOrEmpty(awsSecretKey)) {
if (isNullOrEmpty(accessKey) || isNullOrEmpty(secretKey)) {
// Use the no-argument constructor to fall back on:
// - Environment Variables
// - Java System Properties
// - Instance profile credentials delivered through the Amazon EC2 metadata service
this.emailService = new AmazonSimpleEmailServiceClient();
}
awsAccessKey = this.accessKey;
awsSecretKey = this.secretKey;
}
if (this.emailService == null) {
// Use the supplied credentials.
this.emailService = new AmazonSimpleEmailServiceClient(new BasicAWSCredentials(awsAccessKey, awsSecretKey));
}
if (!isNullOrEmpty(host)) {
this.emailService.setEndpoint(host);
} else if (this.httpsEndpoint != null) {
this.emailService.setEndpoint(this.httpsEndpoint);
}
super.setConnected(true);
return true;
}
@Override
public void close() throws MessagingException {
super.close();
this.emailService = null;
}
/**
*
* The unique message identifier ot the last message sent by sendMessage
*
*
* @return The unique message identifier sent by the last
* sendMessage
action.
*/
public String getLastMessageId() {
return lastMessageId;
}
private static boolean isNullOrEmpty(String s) {
return (s == null || s.length() == 0);
}
private static boolean isNullOrEmpty(Object[] o) {
return (o == null || o.length == 0);
}
public X appendUserAgent(X request, String userAgent) {
request.getRequestClientOptions().appendUserAgent(USER_AGENT);
return request;
}
private static final String USER_AGENT = AWSJavaMailTransport.class.getName() + "/" + VersionInfoUtils.getVersion();
}