📜 ⬆️ ⬇️

Man-hours for implementing the “simple” Email sending module in an application with a modular architecture

On php sending mail is implemented in one line of code! And on java-need 3 weeks ??!
(from conversations with developers and managers)


The article is not about how to send mail to java. My goal is to show the complexities of modular development of large applications (for example, the development of ERP River).

So, the task: to implement the service of sending by email (war).

Stages of development:



Let's start with sending

If not Spring (for a small module it is not needed), connect apache commons-email
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.3</version> </dependency> 

and write
  public class MailSender { public static void sendMail { HtmlEmail email = ... ? email.send(); 

Allow, where to get mail server settings? I think that even the youngest developer will never come up with their hardcode, therefore:

Mail Server Configuration

I hope we have already done the general work of configuring the entire system, and we can only implement it for the mail module:
  ... <part key="email"> <entry key="hostName">smtp.gmail.com</entry> <entry key="user">sendmail@mycompany.ru</entry> <entry key="password">psw</entry> <entry key="smtpPort">465</entry> <entry key="useSSL">true</entry> <entry key="debug">false</entry> <entry key="charset">UTF-8</entry> <entry key="useTLS">false</entry> </part> 

  public class MailConfig { public static <T extends Email> T prepareEmail(T email) { email.setHostName(hostName); email.setSmtpPort(port); email.setSSL(useSSL); email.setTLS(useTLS); email.setDebug(debug); email.setAuthenticator(defaultAuthenticator); email.setCharset(charset); return email; } 

Service call

Yeah, we have a service, how do we want to call it?
Business wants integration over web services, and you also need to have a simple HTTP GET send (for example, call directly from the browser):

Highlighting mail-client

And now how can the neighboring module of our system quickly pull our service on the web service? The easiest way is to select the mail-client maven module, make our mail service dependent on it, and allow any module — our client to include (maven dependency) mail-client:

We fasten patterns

Ege. In the workflow module we have 52 types of documents. It would be good for our customers to give the opportunity to determine the letter pattern themselves. Moreover, such a service (TemplateService) has already been implemented.
The template service is simple: it is implemented on jsp, the key and parameters are sent to get to it, the finished text is returned.


We send the document

Actually, we have documents. But what if we call the service with the document id? Templates for documents in the TemplateService already exist.

fault tolerance

And if the server is temporarily unavailable? It is necessary to save the history in the database and do the add-on ... At the same time, we will solve the problem of sending a letter to the user for the purpose of the BPM task - it can be implemented via a trigger in the database: insert the TODO line into the table. As a side effect, we have a history of sending our messages, then you can wrap ui from above, and just do SQL table queries.
It’s good that we already have a scanning mechanism - just another implementation is needed.



For those who do not like to wait: asynchrony

Since our service is now resilient to failures, we will allow our web service clients to not wait for a response. Instead of duplicating all the services of the service with the Async postfix and the @OneWay annotation, add the async flag and the AsyncExecutor call (our wrapper over ScheduledThreadPoolExecutor) to the MailWSClient calls:
  public class MailWSClient { public static void sendMail(final String from, final String to, final String cc, final String subject, final String body, final String attachmentUrls, boolean async) throws StateException { send(new Runnable() { @Override public void run() { getPort().sendMail(mask(from), mask(to), mask(cc), mask(subject), mask(body), mask(attachmentUrls)); } }, async); } public static void sendTemplateMail(final String from, final String to, final String cc, final String key, final String params, final String attachmentUrls, boolean async) throws StateException { ... public static void sendDocMail(final String from, final String to, final String cc, final String key, final long docId, boolean async) throws StateException { ... private static void send(Runnable task, boolean async) { if (async) { AsyncExecutor.submit(task); } else { task.run(); } } 

We fix image attachments

Well, it works! Finally, you can fix bugs - the pictures in the letter are not visible outside our intranet ... After all, they are set in our templates via <img src = “our internal resources”, naturally, you will not see them in the rest of the world.

Making them inline:
  public class MailSender { static void sendMailAndRecordHistory(String from, String to, String cc, String key, String params, String attachmentUrls, long docId) throws StateException { ... String embedImgBody = MailUtil.embedImg(body, email); public class MailUtil { static final Pattern HTML_URL = Pattern.compile("<img src=(?:\"|')(.+)(?:\"|')", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public static String embedImg(String body, final HtmlEmail email) throws EmailException { return StringUtil.resolveReplacement(body, HTML_URL, new Presentable<Matcher>() { @Override public String toString(Matcher matcher) { String url = matcher.group(1); cid = email.embed(url, UUID.nameUUIDFromBytes(url.getBytes()).toString()); } return "<img src=\"cid:" + cid + "\""; ... 

We send embedded (data: image / png; base64, encoded_img) big pictures

A new business idea - by mistake from the client’s browser, send a screen screenshot to support mail.
The solution on UI is found - the move is ours. For the template service, write the error_mail.jsp template
  <%@page pageEncoding="UTF-8" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Error Report</title> </head> <body> <h2>Error Report from '${user}'</h2> <b>Message:</b> <pre> ${message} </pre> <b>Screenshot:</b><br> <img src="${screenshot}"> </body> </html> 

Template parameters - exception message and base64_encoded_screenshot - are sent to the TemplateService from our service. We have problems: our self-written wrapper MyHttpConnection cannot send base64_encoded_screenshot via GET. You have to do POST and again do URLEncoder.encode because of problems with "+". In addition, in the inline mail that came in, the picture is not visible :( Well, you will also have to do it as an attachment:
  public class MailUtil { static Pattern DATA_PROTOCOL = Pattern.compile("^data:(.+);(.+),"); public static String embedImg(String body, final HtmlEmail email) throws EmailException { return StringUtil.resolveReplacement(body, HTML_URL, new Presentable<Matcher>() { @Override public String toString(Matcher matcher) { String url = matcher.group(1); String cid; try { Matcher m = DATA_PROTOCOL.matcher(url); if (m.find()) { final String cType = m.group(1); final String encoding = m.group(2); final String content = url.substring(m.toMatchResult().end()); cid = email.embed(new javax.activation.DataSource() { @Override public InputStream getInputStream() throws IOException { try { return javax.mail.internet.MimeUtility.decode(new ByteArrayInputStream(IOUtil.getBytes(content)), encoding); } catch (MessagingException e) { throw LOGGER.getIllegalStateException("Image encoding failed", e); } } // empty realization for other javax.activation.DataSource methods ... }, UUID.randomUUID().toString()); } else { cid = email.embed(url, UUID.nameUUIDFromBytes(url.getBytes()).toString()); } return "<img src=\"cid:" + cid + "\""; ... 

Final Point: Security

However, any user sends a get request from the browser - and receives an email with a completely secret document. Not good. It is necessary to fasten the access check of the user to the document with the transmitted docId and generally check: if the request came by get, if the user is logged into our system.
Due to the fact that the login page has already been done and there are a lot of things around it, and we have one entry point, I made a check through REST and domain level cookies with trust to server requests between the modules themselves, but this is already - separate article.

Summary of the simple task of sending mail:

The result was 2 maven modules with classes (not counting infrastructure such as configuration, attachments, templates, common parts and JUnit tests)

A patient reader who has come to the end of the article can himself compare the amount of labor costs for the “task of sending mail” and the resulting implementation. Thanks for attention.



References:

Source: https://habr.com/ru/post/166727/


All Articles