trackr (see here) allows employees to submit vacation requests. When an employee does so, an e-mail is sent out to all supervisors so they can look at it and approve or reject it. One day I had a great idea: what if a supervisor could just answer that e-mail with the text approve and the vacation request gets approved (or rejected respectively)? So I began to research what would have to be done.

Receiving Mails

We use Spring Integration to send e-mails from trackr. Luckily it also supports receiving e-mails. Most of the time reading some documentation and examples is enough to get some code up and running in a short time - props once again to the Spring guys!

Dependencies

First of all you need some dependencies.

dependencies {
    compile "javax.mail:mail:1.4.7"
    compile "jaf:activation:1.0.2"
    compile "org.springframework:spring-context-support:4.0.5.RELEASE"
    compile "org.springframework.integration:spring-integration-mail:4.0.2.RELEASE"
}

(These are all dependencies I have marked as mail in my build.gradle).

Configuration

For Spring Integration I decided to use XML instead of Java config since a previous experience showed me it’s easier (at least for me) to see a workflow in XML. The configuration to receive e-mails and do something with them in a service is actually only three XML elements.

<beans:beans xmlns="http://www.springframework.org/schema/integration"
             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.xsd
                http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
                http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:int-mail="http://www.springframework.org/schema/integration/mail">

    <!-- ... some other stuff to send e-mails -->

    <channel id="receiveChannel" datatype="javax.mail.Message"/>

    <!-- IMPORTANT: the @ sign in the username must be encoded as %40 -->
    <int-mail:imap-idle-channel-adapter id="customAdapter"
                                        store-uri="imaps://username%40gmail.com:${mail.smtp.password}@imap.gmail.com:993/inbox"
                                        channel="receiveChannel"
                                        auto-startup="true"
                                        should-delete-messages="false"
                                        should-mark-messages-as-read="true"
                                        java-mail-properties="mailProperties"/>

    <service-activator input-channel="receiveChannel" ref="mailApproveService" method="approveOrRejectFromMail"/>
</beans:beans>

For this to work you need a bean mailProperties defined, which looks something like this for GMail

@Bean
public Properties mailProperties() {
    Properties mailProperties = new Properties();
    mailProperties.put("mail.smtp.host", "smtp.gmail.com");
    mailProperties.put("mail.smtp.port", "465");
    mailProperties.put("mail.smtp.auth", "true");
    mailProperties.put("mail.smtp.user", "username@gmail.com");
    mailProperties.put("mail.smtp.password", "PASSWORD");
    mailProperties.put("mail.smtp.starttls.enable", "true");
    mailProperties.put("mail.transport.protocol", "smtp");
    mailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    return mailProperties;
}

The method approveOrRejectFromMail in mailApproveService will get called whenever there’s a new mail! It’s signature is:

import javax.mail.Message;

public interface MailApproveService {
    void approveOrRejectFromMail(Message mail);
}

Filtering Mails

The imap-idle-channel-adapter has a really powerful attribute: mail-filter-expression. You can perform some advanced filtering there without the message even being marked as read! For example, mail-filter-expression="subject matches '(?i).*TRACKR.*'" only receives mails that have “TRACKR” in the subject.

Read more in the documentation.

Approving Vacation Requests via Mail

Now that we’ve got a mail in our service we want to do something with it. As mentioned, our first use case was to approve or reject vacation requests.

The first task is to identify a vacation request from the e-mail. Since only supervisors may approve vacation requests and e-mail from addresses are spoofable we need some secret that only the people that got the mail can know. To include the id of the vacation request wouldn’t be enough.

I decided to just add a UUID into the subject and save that UUID along with the id of the vacation request in a mapping table. Other services might use a special e-mail address to map but that wasn’t an option for us.

So when an e-mail arrives I just extract the UUID from the subject, try to find the id of the vacation request in my mapping table and then parse the body of the e-mail for the text approve or reject!

Handling Mail Histories

Once trackr notifies the supervisors about new requests it also informs them that they may respond with either approve or reject. Most people will just reply to the email as is thereby leaving the original text in place. Now what happens is, that trackr receives a mail that contains both terms used to decide whether to approve or reject the request.

To get around this problem we simply count the occurences of both terms and base the decision on whichever term occured more often. This gets the job done. Nevertheless we are open to any other (more sophisticated) solutions - just leave a comment!

Stay in the Loop

If you would like to receive an email every now and then with new articles, just sign up below. We will never spam you!