blob: 0dd0b76340a55f442549aaa0d8092e2a52685744 [file] [log] [blame]
= 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).