| = Custom mail processing components |
| :navtitle: Custom mail processing components |
| |
| When none of the matchers and mailets available in James allows us to implement what we want, extension |
| developers will have to write our own mailet and matcher in a separated maven project depending on James |
| Mailet API. |
| |
| == Writing custom mailets/matchers |
| |
| We will write a *IsDelayedForMoreThan* matcher with a configurable delay. If the Sent Date of incoming |
| emails is older than specified delay, then the emails should be matched (return all mail recipients). Otherwise, |
| we just return an empty list of recipients. |
| |
| To ease our Job, we can rely on the *org.apache.james.apache-mailet-base* maven project, which provides us |
| a *GenericMatcher* that we can extend. |
| |
| Here is the dependency: |
| |
| .... |
| <dependency> |
| <groupId>org.apache.james</groupId> |
| <artifactId>apache-mailet-base</artifactId> |
| </dependency> |
| .... |
| |
| The main method of a matcher is the *match* method: |
| |
| .... |
| Collection<MailAddress> match(Mail mail) throws MessagingException; |
| .... |
| |
| For us, it becomes, with *maxDelay* being previously configured: |
| |
| .... |
| private final Clock clock; |
| private Duration maxDelay; |
| |
| @Override |
| public Collection<MailAddress> match(Mail mail) throws MessagingException { |
| Date sentDate = mail.getMessage().getSentDate(); |
| |
| if (clock.instant().isAfter(sentDate.toInstant().plusMillis(maxDelay.toMillis()))) { |
| return ImmutableList.copyOf(mail.getRecipients()); |
| } |
| return ImmutableList.of(); |
| } |
| .... |
| |
| *GenericMatcher* exposes us the condition that had been configured. We will use it to compute *maxDelay*. |
| We can do it in the *init()* method exposed by the generic matcher: |
| |
| .... |
| public static final TimeConverter.Unit DEFAULT_UNIT = TimeConverter.Unit.HOURS; |
| |
| @Override |
| public void init() { |
| String condition = getCondition(); |
| maxDelay = Duration.ofMillis(TimeConverter.getMilliSeconds(condition, DEFAULT_UNIT)); |
| } |
| .... |
| |
| Now, let's take a look at the *SendPromotionCode* mailet. Of course, we want to write a generic mailet |
| with a configurable reason (why are we sending the promotion code). To keep things simple, only one promotion |
| code will be used, and will be written in the configuration. We can here also simply extend the |
| *GenericMailet* helper class. |
| |
| The main method of a mailet is the *service* method: |
| |
| .... |
| void service(Mail mail) throws MessagingException; |
| .... |
| |
| For us, it becomes, with *reason* and *promotionCode* being previously configured: |
| |
| .... |
| public static final boolean REPLY_TO_SENDER_ONLY = false; |
| |
| private String reason; |
| private String promotionCode; |
| |
| @Override |
| public void service(Mail mail) throws MessagingException { |
| MimeMessage response = (MimeMessage) mail.getMessage() |
| .reply(REPLY_TO_SENDER_ONLY); |
| |
| response.setText(reason + "\n\n" + |
| "Here is the following promotion code that you can use on your next order: " + promotionCode); |
| |
| MailAddress sender = getMailetContext().getPostmaster(); |
| ImmutableList<MailAddress> recipients = ImmutableList.of(mail.getSender()); |
| |
| getMailetContext() |
| .sendMail(sender, recipients, response); |
| } |
| .... |
| |
| Note that we can interact with the mail server through the mailet context for sending mails, knowing postmaster, etc... |
| |
| *GenericMailet* exposes us the 'init parameters' that had been configured for this mailet. We will |
| use it to retrieve *reason* and *promotionCode*. |
| We can do it in the *init()* method exposed by the generic mailet: |
| |
| .... |
| @Override |
| public void init() throws MessagingException { |
| reason = getInitParameter("reason"); |
| promotionCode = getInitParameter("promotionCode"); |
| |
| if (Strings.isNullOrEmpty(reason)) { |
| throw new MessagingException("'reason' is compulsory"); |
| } |
| if (Strings.isNullOrEmpty(promotionCode)) { |
| throw new MessagingException("'promotionCode' is compulsory"); |
| } |
| } |
| .... |
| |
| You can retrieve the sources of this mini-project on https://github.com/apache/james-project/tree/master/examples/custom-mailets[GitHub] |
| |
| == Loading custom mailets with James |
| |
| |
| Now is the time we will run James with our awesome matcher and mailet configured. |
| |
| First, we will need to compile our project with *mvn clean install*. A jar will be outputted in the target directory. |
| |
| Then, we will write the *mailetcontainer.xml* file expressing the logic we want: |
| |
| .... |
| <mailetcontainer enableJmx="true"> |
| |
| <context> |
| <postmaster>postmaster@localhost</postmaster> |
| </context> |
| |
| <spooler> |
| <threads>20</threads> |
| </spooler> |
| |
| <processors> |
| <processor state="root" enableJmx="true"> |
| <mailet match="All" class="PostmasterAlias"/> |
| <mailet match="org.apache.james.examples.custom.mailets.IsDelayedForMoreThan=1 day" |
| class="org.apache.james.examples.custom.mailets.SendPromotionCode"> |
| <reason>Your email had been delayed for a long time. Because we are sorry about it, please find the |
| following promotion code.</reason> |
| <promotionCode>1542-2563-5469</promotionCode> |
| </mailet> |
| <!-- Rest of the configuration --> |
| </processor> |
| |
| <!-- Other processors --> |
| </processors> |
| </mailetcontainer> |
| .... |
| |
| Finally, we will start a James server using that. We will rely on docker default image for simplicity. |
| We need to be using the *mailetcontainer.xml* configuration that we had been writing and position |
| the jar in the *extensions-jars* folder (specific to guice). |