I have so far worked with two ways of sending emails from Java applications.
The first way directly uses the JavaMail API, while the second one utilizes the
Spring framework, which makes it a bit easier to work with the JavaMail API.
The JavaMail Wikipedia page has sufficient examples for the direct use of the
JavaMail API . This post shows code
examples of the Spring one. We are implementing a Spring-based REST service
for sending emails.
Maven dependencies
The Spring support for emails is provided in the spring-context-support
artifact, and we also need the JavaMail artifact. Therefore, the following
dependencies are needed in the pom.xml file:
<dependency>
<groupId> org.springframework</groupId>
<artifactId> spring-context-support</artifactId>
<version> ${spring.version}</version>
</dependency>
<dependency>
<groupId> javax.mail</groupId>
<artifactId> mail</artifactId>
<version> 1.4.7</version>
</dependency>
The dependency dumbster
provides a fake SMTP server, to be used in the unit
tests:
<dependency>
<groupId> com.github.kirviq</groupId>
<artifactId> dumbster</artifactId>
<version> 1.7.1</version>
<scope> test</scope>
</dependency>
The mail sending service implementation
We implement the following MailService interface, which can be used by, e.g., a Spring RestController
to expose an email-sending REST service.
package io.github.ouyi.mail.service ;
import javax.mail.MessagingException ;
public interface MailService {
void sendMail ( String to , String subject , String bodyText , String attachmentPath ) throws MessagingException ;
}
The implementation looks like this:
package io.github.ouyi.mail.service ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.core.io.FileSystemResource ;
import org.springframework.mail.javamail.JavaMailSender ;
import org.springframework.mail.javamail.MimeMessageHelper ;
import javax.mail.MessagingException ;
import javax.mail.internet.MimeMessage ;
import java.util.Optional ;
public class MailServiceImpl implements MailService {
private final JavaMailSender mailSender ;
private final String from ;
@Autowired
public MailServiceImpl ( JavaMailSender mailSender , String from ) {
this . mailSender = mailSender ;
this . from = from ;
}
@Override
public void sendMail ( String to , String subject , String bodyText , String attachmentPath ) throws MessagingException {
send ( from , to , subject , bodyText , attachmentPath , ! Optional . ofNullable ( attachmentPath ). orElse ( "" ). isEmpty ());
}
private void send ( String from , String to , String subject , String bodyText , String attachmentPath , boolean multipart ) throws MessagingException {
MimeMessage message = mailSender . createMimeMessage ();
MimeMessageHelper helper = new MimeMessageHelper ( message , multipart );
helper . setFrom ( from );
helper . setTo ( to );
helper . setSubject ( subject );
helper . setText ( bodyText );
if (! Optional . ofNullable ( attachmentPath ). orElse ( "" ). isEmpty ()) {
FileSystemResource file = new FileSystemResource ( attachmentPath );
helper . addAttachment ( file . getFilename (), file );
}
mailSender . send ( message );
}
}
Note a multipart message is required in order to send an email with attachment.
Unit tests
As mentioned earlier, we use dumbster
for unit testing:
package io.github.ouyi.mail.service ;
import com.dumbster.smtp.SimpleSmtpServer ;
import com.dumbster.smtp.SmtpMessage ;
import org.junit.Test ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import org.springframework.mail.javamail.JavaMailSenderImpl ;
import java.io.FileNotFoundException ;
import java.io.IOException ;
import java.util.Properties ;
import static org . junit . Assert . assertEquals ;
public class MailServiceImplTest {
private static final Logger logger = LoggerFactory . getLogger ( MailServiceImplTest . class );
private static final String HEADER_KEY_TO = "To" ;
private static final String HEADER_KEY_SUBJECT = "Subject" ;
@Test
public void testSendMail () throws Exception {
String smtpHost = "localhost" ;
int smtpPort = 12345 ;
String subject = "MY_SUBJECT" ;
String messageBody = "MY_MESSAGE_BODY" ;
String fromAddr = "sender@localhost" ;
String toAddr = "test@test.com" ;
logger . info ( "Start dumbster fake SMTP server..." );
SimpleSmtpServer server = SimpleSmtpServer . start ( 12345 );
logger . info ( "Send mail message" );
JavaMailSenderImpl mailSender = new JavaMailSenderImpl ();
Properties properties = new Properties ();
properties . setProperty ( "mail.smtp.host" , smtpHost );
properties . setProperty ( "mail.smtp.port" , String . valueOf ( smtpPort ));
mailSender . setJavaMailProperties ( properties );
MailServiceImpl mailService = new MailServiceImpl ( mailSender , fromAddr );
mailService . sendMail ( toAddr , subject , messageBody , null );
server . stop ();
assertEquals ( 1 , server . getReceivedEmails (). size ());
SmtpMessage email = server . getReceivedEmails (). get ( 0 );
assertEquals ( toAddr , email . getHeaderValue ( HEADER_KEY_TO ));
assertEquals ( subject , email . getHeaderValue ( HEADER_KEY_SUBJECT ));
assertEquals ( messageBody , email . getBody ());
}
}
Dependency injection
Since we use Spring, we can use dependency injection to create and configure a JavaMailSenderImpl
instance, which can be injected into the
MailServiceImpl
instance:
<beans xmlns= "http://www.springframework.org/schema/beans"
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation= "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >
<bean id= "mailSender" class= "org.springframework.mail.javamail.JavaMailSenderImpl" >
<property name= "host" value= "my.smtp.host" />
<property name= "port" value= "587" />
<property name= "username" value= "user" />
<property name= "password" value= "pass" />
<property name= "javaMailProperties" >
<props>
<prop key= "mail.smtp.auth" > true</prop>
<prop key= "mail.smtp.starttls.enable" > true</prop>
</props>
</property>
</bean>
<bean id= "mailService" class= "com.hellokoding.account.service.MailServiceImpl" >
<constructor-arg ref= "mailSender" ></constructor-arg>
<constructor-arg type= "java.lang.String" value= "sender@mydomain.com" ></constructor-arg>
</bean>
</beans>
The REST controller
package io.github.ouyi.web ;
import java.io.BufferedOutputStream ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import javax.servlet.http.HttpServletRequest ;
import javax.servlet.http.HttpServletResponse ;
import javax.servlet.http.HttpSession ;
import javax.ws.rs.core.Response ;
import org.apache.poi.xssf.usermodel.XSSFWorkbook ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.stereotype.Controller ;
import org.springframework.web.bind.annotation.RequestMapping ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import org.springframework.web.bind.annotation.ResponseBody ;
@Controller
public class MailController {
private final Logger logger = LoggerFactory . getLogger ( MailController . class );
@Autowired
private MailService mailService ;
@Autowired
private String mailTo ;
private static final String MAIL_SUBJECT = "medshop order" ;
private static final String MAIL_CONTENT = "Please find the order in the attachment." ;
@RequestMapping ( value = { "/sendmail" }, method = RequestMethod . POST , produces = "application/json" )
public @ResponseBody Response sendmail ( HttpServletRequest request , HttpServletResponse response ) throws Exception {
File tempFile = null ;
OutputStream outputStream = null ;
try {
tempFile = File . createTempFile ( "mail-attachment-" , ".xlsx" );
outputStream = new BufferedOutputStream ( new FileOutputStream ( tempFile ));
XSSFWorkbook document = fileService . createSpreadsheet ();
document . write ( outputStream );
outputStream . flush ();
mailService . sendMail ( mailTo , MAIL_SUBJECT , MAIL_CONTENT , tempFile . getAbsolutePath ());
} finally {
if ( outputStream != null ) {
outputStream . close ();
}
if ( tempFile != null ) {
tempFile . delete ();
}
}
return Response . ok (). build ();
}
}
The sendmail
method of the MailController
has to return a Response
object. When I changed it to void
, I got the following error response from my GlassFish server:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns= "http://www.w3.org/1999/xhtml" ><head><title> GlassFish Server Open Source Edition 5.0 - Error report</title><style type= "text/css" > <! --H1 { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ; font-size : 22px ;} H2 { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ; font-size : 16px ;} H3 { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ; font-size : 14px ;} BODY { font-family : Tahoma , Arial , sans-serif ; color : black ; background-color : white ;} B { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ;} P { font-family : Tahoma , Arial , sans-serif ; background : white ; color : black ; font-size : 12px ;} A { color : black ;} HR { color : #525D76 ;} -- > </style> </head><body><h1> HTTP Status 405 - Request method & #39; GET& #39; not supported</h1><hr/><p><b> type</b> Status report</p><p><b> message</b> Request method & #39; GET& #39; not supported</p><p><b> description</b> The specified HTTP method is not allowed for the requested resource.</p><hr/><h3> GlassFish Server Open Source Edition 5.0 </h3></body></html>
When it returns a Response
object but without the annotation @ResponseBody
, I got another error response:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns= "http://www.w3.org/1999/xhtml" ><head><title> GlassFish Server Open Source Edition 5.0 - Error report</title><style type= "text/css" > <! --H1 { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ; font-size : 22px ;} H2 { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ; font-size : 16px ;} H3 { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ; font-size : 14px ;} BODY { font-family : Tahoma , Arial , sans-serif ; color : black ; background-color : white ;} B { font-family : Tahoma , Arial , sans-serif ; color : white ; background-color : #525D76 ;} P { font-family : Tahoma , Arial , sans-serif ; background : white ; color : black ; font-size : 12px ;} A { color : black ;} HR { color : #525D76 ;} -- > </style> </head><body><h1> HTTP Status 404 - Not Found</h1><hr/><p><b> type</b> Status report</p><p><b> message</b> Not Found</p><p><b> description</b> The requested resource is not available.</p><hr/><h3> GlassFish Server Open Source Edition 5.0 </h3></body></html>
Calling the REST service using JQuery and Ajax
Our mail-sending REST service can be consumed by the frontend JavaScript code as follows:
$ ( document ). ready ( function ( event ) {
$ . ajaxPrefilter ( function ( options , originalOptions , jqXHR ) {
jqXHR . setRequestHeader ( " ${_csrf.headerName} " , " ${_csrf.token} " );
});
$ ( ' #sendmail-button ' ). click ( function () {
$ . ajax ({
type : ' post ' ,
url : " ${contextPath}/sendmail " ,
dataType : ' json '
}). done ( function ( response ) {
console . log ( response );
alert ( " Email sent successfully! " );
}). fail ( function ( response ) {
console . log ( response );
alert ( " Failed to send email! " );
});
});
});
Note that ${_csrf.headerName}
, ${_csrf.token}
, and ${contextPath}
are
templated on the server-side using jsp. If server-side templating is not used,
the CSRF header name and token can be passed to the client as meta tags in the
HTML head section, which can then be retrieved by JavaScript code. Details can be found in the Spring security
documentation .
The context path, which is quite static, can be easily injected into the frontend code at build time.