These values were selected to support the reliable delivery goal demonstrated in the proof of concept.
A production implementation would tune these values based on operational requirements, expected outage duration, message urgency, and business expectations.
The important part is that Artemis avoids continuously hammering an unavailable SMTP server while still preserving messages for later delivery.
Production Considerations
The proof of concept intentionally focuses on a single failure mode:
SMTP server unavailable.
That was the original customer scenario, and it is the scenario this proof of concept validates.
A production implementation would need additional handling for permanent failures.
Examples include:
- Invalid recipient addresses.
- Invalid sender addresses.
- Oversized messages.
- Malformed MIME content.
- Domain resolution failures.
- Mail server policy rejections.
- Missing required message fields.
There has been a lot of work trying to minimize the use of the ext environment to keep your site more maintainable and easier to customize. Here are some tips that has helped me keep my ext small and more maintainable/manageable which will also make upgrades more smooth.
Retrying these messages forever usually doesn't make sense.
A production implementation would likely inspect delivery failures and make routing decisions based on the type of failure.
Some messages should remain in the send queue because the failure is transient.
Some messages should move to a dead letter queue.
Some messages should move to an undeliverable queue.Some messages may require manual review.
Some messages may be safe to discard according to business policy.
A more complete production topology might look like this:

The correct handling depends entirely on business requirements.
A password reset email might be discarded after a reasonable expiration window.
A purchase confirmation email might require manual review.
A regulatory notification might require alerting and escalation.
The important point is that these decisions happen after the message has already been durably persisted.
The email request is never silently lost because of a temporary infrastructure failure.
Move anything possible in ext to hooks.What About Custom Email Code?
This solution works automatically for applications using Liferay's standard mail APIs.
If custom code bypasses Liferay's mail infrastructure and connects directly to SMTP, those messages will not pass through the durable queue.
Additional customization would be required to bring those emails into the same processing pipeline, but it's not that complicated. You're already preparing a MailMessage
and then invoking 1. Do you need to override a *ServiceImpl.java class? (v6.0 or later)Transport.send()
with it. Instead of sending it, take a look at how JmsMailDivertListener is posting the mail to the JMS queue. Your custom code would do the same thing and the rest of the implementation will just work.
This is an important operational detail.
Final Thoughts
Final Thoughts
The interesting thing about this project is that it started with an email question and ended with a messaging solution.
The customer wasn't really asking how to send email more reliably.
<hook>They were asking how to ensure an important business event could not be lost.
Durable messaging solves that problem.
<service>
And it solves the problem regardless of the scenario, whether it is simply sending an email or updating a record in an external system or writing a file to the filesystem... They're all the same - it's an important business event that cannot be lost, and an outcome that must be reliably satisfied. JMS and durable queues are the way to ensure that.
For reliable email delivery, once email requests are persisted in a durable queue, temporary infrastructure outages stop being catastrophic events and become operational inconveniences.
<service-type>com.liferay.portal.service.UserLocalService</service-type>Liferay provides a clean interception point through the Liferay Message Bus.
JMS provides queue durability.
Apache Camel provides integration.
Together they create a relatively simple solution for a problem many organizations assume requires expensive proprietary infrastructure.
And perhaps the biggest lesson is this:
When something absolutely must happen, don't rely on immediate execution.
</service>Publish (enqueue) the intent first.
Then process it reliably.
</hook>
URL for the repo if you didn't see it already:
Then in your class just extend the UserLocalServiceWrapper and override any methods you want.
public class TestUserLocalServiceImpl extends UserLocalServiceWrapper {
Check out the test-hook portlet located in svn://svn.liferay.com/repos/public/plugins/trunk/portlets/test-hook-portlet/ for more info.
2. Do you need to override any classes listed in the liferay-hook.dtd or do you need to add event actions?
For example, do you need to override the ScreennameValidator, ScreennameGenerator, Document library hook, or add postLoginActions, servicePreActions, etc.? These can be overriden through portal.properties in a hook and even allow hot deploy overriding! That means you don't need to restart tomcat to test a new change, you can just redeploy your hook. See the full list in lifeary-hook_6_0_0.dtd. New properties will continually be added so keep an eye for new ways to minimize your ext.
Use custom attributes.
1. Do you need to customize user attributes?
Instead of modifying service.xml to add a new column and regenerating all the services, use a custom attribute. There is practically no performance difference, String attributes (only string!) are indexed, and you can do it through the admin GUI. One thing you need to keep in mind is to give view or update permissions apprpriately.
They are even picked up automatically in the create account form! You don't even need to modify any java classes to add a new attribute for registering. Just create a JSP hook and add this easy taglib to the create_account.jsp.
<liferay-ui:custom-attribute
className="com.liferay.portal.model.User"
classPK="<%= 0 %>"
editable="<%= true %>"
label="<%= true %>"
name="favoriteColor"
/>
In the action class, it will pick any custom attributes and update them accordingly. The same is true for most all other models too (ie Documents, Images, Web content).
Extend rather than override.
1. Do you need to override any *Action.java classes?
Any classes in struts-config.xml or liferay-portlet.xml should be extended rather than overrided. This will hopefully make upgrading less painfulby keeping the current code and just adding your own or tweaking variables so you get the behaviour you want.
For example, say you created a custom attribute "Favorite Color" and you want to make it required to be filled out.
<struts-config>
<action-mappings>
<action path="/login/create_account" type="com.liferay.test.ext.portlet.login.action.TestCreateAccountAction">
<forward name="portlet.login.create_account" path="portlet.login.create_account" />
</action>
</struts-config>
Then in your class:
public class TestCreateAccountAction extends CreateAccountAction {
protected void addUser(ActionRequest actionRequest, ActionResponse actionResponse)
throws Exception {
Map<String, Serializable> expandoBridgeAttributes =
PortalUtil.getExpandoBridgeAttributes(
ExpandoBridgeFactoryUtil.getExpandoBridge(User.class.getName()), actionRequest);
String favoriteColor = (String)expandoBridgeAttributes.get("favoriteColor");
if (Validator.isNull(favoriteColor)) {
throw new RequiredFieldException("favoriteColor", "favoriteColorLabel"); // v6.0/EE specific code
}
super.addUser(actionRequest, actionResponse); // Don't touch current code to make upgrades painless =)
}
}
2. Do you need to override JSPs?
Use a JSP hook. (See www.liferay.com/community/wiki/-/wiki/Main/Portal+Hook+Plugins)
And use the buffer util taglib to achieve a similar affect to what I did with CreateAccountAction.java class above. Let's say we want to remove the javascript on the bottom, change the word "save" to "create" and add the custom attribute favorite color to the registration form after the last name.
<%@ include file="/html/portlet/login/init.jsp" %>
<liferay-util:buffer var="html">
<liferay-util:include page="/html/portlet/login/create_account.portal.jsp" />
</liferay-util:buffer>
<liferay-util:buffer var="customHtml">
<liferay-ui:custom-attribute
className="com.liferay.portal.model.User"
classPK="<%= 0 %>"
editable="<%= true %>"
label="<%= true %>"
name="favoriteColor"
/>
</liferay-util:buffer>
<%
int x = html.lastIndexOf("<script type=\"text/javascript\"");
if (x != -1) {
y = html.indexOf("</script>", x);
html = html.substring(0, x) + html.substring(y + 9);
}
html = html.replace(LanguageUtil.get(pageContext, "save"), LanguageUtil.get(pageContext, "create"));
x = html.indexOf(LanguageUtil.get(pageContext, "last-name"));
if (x != -1) {
y = html.indexOf("</div>", x);
html = html.substring(0, y) + customHtml + html.substring(y);
}
%>
<%= html %>
Conclusion
Hopefully that was useful to you. Of course, this isn't always possible and you'll have to resort to direct overwriting. But I hope this can minimize the pain of upgrading for some people.
If there are commonly overrided classes, let's see if we can figure out ways to make it easier, make it possible to use a technique mentioned above, or be moved to a hook =). Or...contribute to trunk if it's useful, that way we maintain it for you and others also benefit from it.
If you guys have any other methods you like to use or if I missed anything, please leave a comment.

