Home
 

Simple Windows SMTP Server

Apache James - Platform Independent SMTP Services and General Email Server Platform

While looking for an email server to assist with some development I was doing for a client, I had the need to search for such a solution for Windows Vista. After reading a few evaluations and a few false starts, I came up with a solution based on Apache James that seems to meet my needs quite well. As a result of not finding this solution documented anywhere, I wanted to make this configuration available to others with similar needs.

Background

While doing some work for a client the other day, I had a need to test some application integration for an application we were developing with an email system. I has assumed SMTP as the transport, but as I always want to test end to end, I found myself in a situation where I didn't want to use a "live" email server for my testing as I didn't actually want the emails to go anywhere while I was testing as it was sufficient to assume that once a successful hand off to an SMTP server was accomplished that the integration was successful. However, I also needed the system to be able to store the emails so that I could verify the various aspects of the emails sent, such as header (to information, subject, etc) and the message body.

While I did not find very much good information, I eventually (seems it was rather roundabout) found a solution and wanted to document my approach and implementation of the solution in order to assist others that may be looking for a similar solution.

Requirements

My requirements for this solution were rather simple. I needed something that would simulate/act like/or was an SMTP server. It needed to be able to be started and stopped rather easily, but did not have to be a daemon or Windows service. I was preferring a solution that could run on my Windows Vista laptop, where I was doing the development. I needed the messages persisted and in a format that they could be easily verified (text file was preferred). Finally, I had to be able to control the delivery of the messages so that they did not actually get sent into "the wild". The following summarizes these requirements:

  • Easily started and stopped
  • Listen for incoming connections on port 25 (SMTP)
  • Persist emails in human readable format
  • Runs on Windows Vista
  • Control delivery (or non-delivery in this case) of emails

Solutions Considered

I considered a couple of solutions prior to settling on Apache James. My reasoning for listing these solutions here is that even though I decided to pass on them, they may meet your needs and you might find them of interest.

The first solution was to remember that Windows had (previously) had an SMTP server. Seems I even remember it still being an option with Windows XP; however, Windows Vista does not have an option to install an SMTP server, so one idea down. My next endeavor was to google a number of terms, which produced limited value. One solution at the top of the google list (Google "Vista SMTP Server") was a number of free solutions, but none of them looked really promising. The one posted on channel9 looked interesting, but I am generally suspicious of solutions that are not at least somewhat broadly accepted (a personal and informal measurement by generally measuring by the number of reviews and comments rather than a quantitative measurement and obviously biased by my personal opinion).

Another potential solution I evaluated and had high hopes for was from SoftStack, but I was also suspicious of this; however you may find it meets your needs. Again, I kept looking and turned to sourceforge. Once there, I made my way to the MTA (Mail Transport Agent) section of the site and found a couple of promising candidates, but most were POSIX only. One that did look interesting was Java SMTP/POP Email Server, but I decided to pass on it (yeah, I can be pretty picky); however, I considered this the most promising candidate so far. However, I had pretty much decided to use Cygwin (which I often use for other purposes as well) and see what they provided. However, for once, I didn't find what I had hoped (postfix is what I primarily use on Linux as an MTA) and while they do offer exim as an option, I wasn't looking forward to learning yet another MTA.

It does seem however, that in the process of checking on the status of postfix on cygwin or the reviews of exim, I somehow ran across the Apache James project. As I have been using quite a few Apache projects here lately (ActiveMQ, Forrest, Cocoon, and Lenya to name a few), I was excited to see a potential solution there and I rushed to the James project website. While my enthusiasm dimmed just a bit and the configuration and learning curve took longer than I had hoped (but still not that bad, something like a few hours investment), I was eventually impressed at how well James met my needs and how flexible it could be.

Another solution that I have recently discovered and probably would have considered and used, before my customization of Apache James is fakemail. It comes in PERL and Python flavors, so as long as you have one of these environments available on your platform, it might be worth your while to take a look at it.

As a result, I am now using Apache James in addition to the other Apache projects and have created this document to help others understand how to quickly setup and leverage Apache James as a development email server. One additional benefit is that since James is Java based, you can leverage this solution on any platform that supports a JRE.

Configuring Apache James v2.3.1

While I am really liking the state of OSS (Open Source Software) these days, one aspect that continues to disappoint is the stark lack of good documentation. However, this has to a greater or lesser degree been an issue with technology for as long as I have been working with technology (over 30 years now), with only a few exceptions and most of the exceptions are now gone, having been slain by the giants (except IBM, which from memory usually had pretty good documentation overall). The other major issue is that even if you can find documentation, it is often out of date with respect to the product (or the version for which it applies is not clear, as is the case for documentation outside of the site maintained by the projects). So instead of just complaining about the situation, I have decided to take action and work to at least provide some documentation over the aspects I have learned and hopefully contribute something back to the community.

In defence of the James project, it did seem that they had pretty good documentation, especially in comparison to other OSS projects I am using (which shall remain nameless so that we can blame the innocent and reward the guilty <grin>). Additionally, the config file that comes with the distribution has allot of comments, so it wasn't too difficult to figure out what I needed to do, but it sure would have been easier if someone has "given" me the solution (like I am hopefully doing for you!)...

First, hop on over to the Apache James website and download the latest version of the binary package. As of the writing of this document and the one that I used to setup this environment, it is James version 2.3.1 and the binary is called james-binary-2.3.1.zip or james-binary-2.3.1.tar.gz. Grab the one that strikes your fancy best (I generally use *.tar.gz even on Windows, as I use 7-zip (don't worry, it's OSS under the GNU LGPL and also available on sourceforge) which can deal with gziped tar files even on Windows.

I have put/installed all my Apache Software Foundation (ASF) projects under the "C\asf" directory instead of the typical "C\Program Files", due to issues with the "new" UAC (User Account Control) on Windows Vista that is "supposed to make our lives easier" <groan>. I know, I could turn it off, but in the hopes that it does actually "save me" at some point in the future and also because most of the rest of the population don't know how and thus live with it, I figured I might as well suffer, especially since if I turn it off and then do something that wouldn't work with it on and then try to distribute the solution or write a document such as this, it has the potential to cause problems... Oh well, my solution is to put most of my applications (especially ASF ones) outside of the "hot zone". I am sure that someone will tell me why this is a bad idea and the ones "in the know" will say that the application should be changed to understand (and cope with the impositions of Microsoft <roll>), but alas, "onward and upwards"...

For now, I would suggest unzipping somewhere outside of "C:\Program Files" due to the fact that you will be editing the config and it will be writing the emails to the file system and you avoid the issues introduced with UAC on Vista. You can review the documentation for the "Server" sub-project (which is the one that is downloaded with the file names above, which isn't especially obvious or intuitive at first blush) on James site and I must say the documentation is pretty good and comprehensive. However, if you want the "spoon fed" version, read on; however, I would suggest that at some point you at least peruse the documentation, if for no other reason that to understand additional features of capabilities for which you might have a need or just find interesting. As an aside, I did find the Architecture of James to be interesting and in general am impressed with the Apache products and how they are architected and built on other solutions (generally within the ASF stack).

The configuration files will not be unpacked until you run James for the first time, so it is best to go ahead and execute the "run" script in the bin directory and then kill the server so that you can have access to the configuration. The configuration file is relatively intuitive, even without having to dig deeply into the documentation and as mentioned earlier, the default config file has a significant amount of commenting provided. This made it pretty easy to get what I wanted without having to spend hours pouring over documentation. There were a few gotchas that I had to resort to the documentation to figure out or that I figured out after reviewing the documentation, so you can do allot without having to invest allot of time, but when you do need it, the documentation is pretty good.

A couple of quick notes on the config file. First is it's location, which was not immediately obvious, but if you RTFM (I'll leave this to someone else to fill you in on this if you don't know what it means) it is pretty much covered up front, but for the impatient ones among us, if you unziped into the "C:\asf" directory and you downloaded the 2.3.1 version of the correct binary file, it is located at: "C:\asf\james-2.3.1\apps\james\SAR-INF\config.xml". You will have to start James at least once to get it to unpack the "sar" file so that you can modify the configuration. More information can be found on this topic on the James installation page.

Second thing to note is that I have stripped out all the comments from the original config file from the distribution. As a matter of habit (and as a suggestion to the reader), I renamed the file "config.xml.orig" prior to making modifications. I also intermittently saved a copy to "config.xml.save" once I had a working config before making additional changes. I would suggest the practice to anyone that is willing to listen. If you think that is "wacky", I generally add my complete "/etc" and specific "/var/*" directories to subversion for all my servers! You don't know how handy it can be when you have been "tweaking" a server only to find you broke something and don't know which specific change broke the server and need to back out the changes! Backups work well too, but then you have to keep up with when changes were made and doing diffs is not quite as easy as subversion... There are some potential security issues created by this practice, and while I am aware of most of them, I would urge you to give some thought to those issues prior to undertaking the practice, but it has saved my butt on numerous occasions. Now back to the regularly scheduled program...

A third consideration, and one I found through trial and error (I am sure it is in the documentation somewhere, but I never saw it) all the file paths (especially in the "file://...") are relative to the "[install dir]/apps/james/" directory, so when you go looking for the emails, this is where to start and then follow the appropriate path as specified in the config file. I am not sure if it is possible to locate these directories outside of the above location, but a more thorough review of the documentation might shed more light on the subject. Just be aware of this if you plan to change the file locations. Also, if I had known this, I would have left the file paths the way they came, but I was expecting the paths relative to the root of the drive ("C:\" in this specific case) and didn't want to have James creating yet another directory in the root, when I have a "C:\data\" directory that I generally try to use for collecting data files from most applications. As stated, this was not the case as the directories are located relative to what I assume is where the application server (Phoenix in this case which is another Apache project) believes is the root of the application.

Final note, there may be additional items that can be removed from this config file and/or additional tweaks to refine resource usage. I make no guarantees that this configuration is optimal, I only know that I stripped as much as I could out while still leaving enough to accomplish my goal of having a usable SMTP server that dumped the emails to the file system without attempting to actually transfer the emails outside of the box. Again, more insight can probably be gained by RTFM.

Given my needs, enumerated above, the following is my complete config file. I will explain the various "deviations" I made from the defaults in more detail below.

<?xml version="1.0"?>
<!DOCTYPE config [
<!ENTITY listserverConfig SYSTEM "../conf/james-listmanager.xml">
<!ENTITY listserverStores SYSTEM "../conf/james-liststores.xml">
<!ENTITY fetchmailConfig SYSTEM "../conf/james-fetchmail.xml">
<!ENTITY smtphandlerchainConfig SYSTEM "../conf/james-smtphandlerchain.xml">
]>

<config>
    <James>
        <postmaster>Postmaster@localhost</postmaster>
        <servernames autodetect="false" autodetectIP="false">
            <servername>localhost</servername>
        </servernames>
        <usernames ignoreCase="true" enableAliases="true" enableForwarding="false"/>
        <inboxRepository>
            <repository destinationURL="file://data/mail/inboxes/" type="MAIL"/>
        </inboxRepository>
    </James>

    <mailetpackages>
        <mailetpackage>org.apache.james.transport.mailets</mailetpackage>
        <mailetpackage>org.apache.james.transport.mailets.smime</mailetpackage>
    </mailetpackages>

    <matcherpackages>
        <matcherpackage>org.apache.james.transport.matchers</matcherpackage>
        <matcherpackage>org.apache.james.transport.matchers.smime</matcherpackage>
    </matcherpackages>

    <spoolmanager>
        <threads> 5 </threads>

        <processor name="root">
            <mailet match="All" class="ToRepository">
                <repositoryPath>file://data/mail/local/</repositoryPath>
            </mailet>
        </processor>

        <processor name="error">
            <mailet match="All" class="ToRepository">
                <repositoryPath> file://var/mail/error/</repositoryPath>
            </mailet>
        </processor>
    </spoolmanager>

    <dnsserver>
        <servers>
        <server>127.0.0.1</server>
        </servers>
        <autodiscover>false</autodiscover>
        <authoritative>false</authoritative>
        <maxcachesize>50000</maxcachesize>
    </dnsserver>

    <remotemanager enabled="false" />

    <pop3server enabled="false" />

    <smtpserver enabled="true">
        <port>25</port>
        <bind>127.0.0.1</bind>
        <handler>
            <helloName autodetect="false">localhost</helloName>
            <connectiontimeout>360000</connectiontimeout>
            <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
            <maxmessagesize>0</maxmessagesize>
        </handler>
    </smtpserver>

    <nntpserver enabled="false" />

    <nntp-repository>
        <readOnly>false</readOnly>

        <rootPath>file://data/nntp/groups</rootPath>
        <tempPath>file://data/nntp/temp</tempPath>
        <articleIDPath>file://data/nntp/articleid</articleIDPath>
        <articleIDDomainSuffix>news.james.apache.org</articleIDDomainSuffix>

        <newsgroups>
            <newsgroup>org.apache.james.dev</newsgroup>
            <newsgroup>org.apache.james.user</newsgroup>
            <newsgroup>org.apache.avalon.dev</newsgroup>
            <newsgroup>org.apache.avalon.user</newsgroup>
        </newsgroups>

        <spool>
            <configuration>
                <spoolPath>file://data/nntp/spool</spoolPath>
                <threadCount>0</threadCount>
                <threadIdleTime>60000</threadIdleTime>
            </configuration>
        </spool>
    </nntp-repository>

    <spoolrepository destinationURL="file://data/mail/spool/" type="SPOOL"/>

    <mailstore>
        <repositories>

            <repository class="org.apache.james.mailrepository.AvalonMailRepository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>MAIL</type>
                </types>
            </repository>

            <repository class="org.apache.james.mailrepository.AvalonSpoolRepository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>SPOOL</type>
                </types>
            </repository>

            <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Object_Repository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>OBJECT</type>
                </types>
                <models>
                    <model>SYNCHRONOUS</model>
                    <model>ASYNCHRONOUS</model>
                    <model>CACHE</model>
                </models>
            </repository>

            <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Stream_Repository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>STREAM</type>
                </types>
                <models>
                    <model>SYNCHRONOUS</model>
                    <model>ASYNCHRONOUS</model>
                    <model>CACHE</model>
                </models>
            </repository>

        </repositories>
    </mailstore>

    <users-store>
        <repository name="LocalUsers" class="org.apache.james.userrepository.UsersFileRepository">
            <destination URL="file://data/mail/users/"/>
        </repository>
    </users-store>

    <database-connections />

    <connections>
        <idle-timeout>300000</idle-timeout>
        <max-connections>30</max-connections>
    </connections>

    <sockets>
        <server-sockets>
            <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultServerSocketFactory"/>
        </server-sockets>

        <client-sockets>
            <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultSocketFactory"/>
        </client-sockets>
    </sockets>

    <thread-manager>
        <thread-group>
            <name>default</name>
            <priority>5</priority>
            <is-daemon>false</is-daemon>
            <max-threads>20</max-threads>
            <min-threads>5</min-threads>
            <min-spare-threads>5</min-spare-threads>
        </thread-group>
    </thread-manager>
</config>

Another quick note now that I have provided the config file is that the configuration I am using is a very poor example of the capabilities of this software. I must again say that I was significantly impressed with the architecture of this project and it's flexibility especially the flexibility exposed and provided by the config file. Please see the James project site or review the out of the box config file for a more comprehensive overview of it's capabilities.

A number of sections of the config are pretty much as they existed in the out of the box configuration and I will not provide comment on those, as the comments in the original config file or the documentation provided on the project site do a much better job. One small exception is the "thread-manager" section where I tweaked the threads numbers a little just to limit the resources consumed by the server.

Another small change, as was suggested in the comments in the original config file is to the "nntp-repository" configuration section where I changed the value of the "nntp-repository/spool/configuration/threadcount" value to zero, since I had disabled the nntp module. Again, this was suggested in the comments provided in the original configuration file. For more information consult the original configuration file or the project documentation.

<James />

    <James>
        <postmaster>Postmaster@localhost</postmaster>
        <servernames autodetect="false" autodetectIP="false">
            <servername>localhost</servername>
        </servernames>
        <usernames ignoreCase="true" enableAliases="true" enableForwarding="false"/>
        <inboxRepository>
            <repository destinationURL="file://data/mail/inboxes/" type="MAIL"/>
        </inboxRepository>
    </James>

The changes that I made to the "James" section of the configuration were only to remove the comments and commented sections and change the values of some of the parameters. If I have a chance, I will review the documentation, as I would be willing to bet that I can remove a number of the remaining settings or just leave closed tags ( <servername /> ) and further distill the config file; however, these seemed to be minor with regard to resource consumption and the solution I required, so I did not fret too much over them.

The specific changes made to this file were to turn off auto detection of the servername and the IP addresses and set the servername to "localhost". This was because I didn't want the SMTP server listening on anything but 127.0.0.1 as I didn't want anyone but local applications (local to the computer on which it was running) to be able to connect to it. I would not advise this configuration for anything but a development test SMTP server for obvious reasons.

<mailetpackages /> & <matcherpackages />

    <mailetpackages>
        <mailetpackage>org.apache.james.transport.mailets</mailetpackage>
        <mailetpackage>org.apache.james.transport.mailets.smime</mailetpackage>
    </mailetpackages>

    <matcherpackages>
        <matcherpackage>org.apache.james.transport.matchers</matcherpackage>
        <matcherpackage>org.apache.james.transport.matchers.smime</matcherpackage>
    </matcherpackages>

I made no changes to these sections. I probably could have removed the "s/mime" objects for my needs; however, it didn't seem significant to just leave these as they were.

<spoolmanager />

    <spoolmanager>
        <threads> 5 </threads>

        <processor name="root">
            <mailet match="All" class="ToRepository">
                <repositoryPath>file://data/mail/local/</repositoryPath>
            </mailet>
        </processor>

        <processor name="error">
            <mailet match="All" class="ToRepository">
                <repositoryPath> file://var/mail/error/</repositoryPath>
            </mailet>
        </processor>
    </spoolmanager>

This is the section with the most significant change from the out of the box configuration and where it seems the majority of the "magic" happens within James. At least, this appears to be where it all gets "wired together".

Basically, since I just needed a server to listen for SMTP traffic on port 25, I removed most of the functionality in this section. To me, the default configuration was pretty intuitive out of the box, while my configuration will probably be much less so, this is due to the fact that I removed most of the functionality provided by the product. I at one time had also removed the error processor, but I noticed through my testing that it was being requested and upon reviewing the documentation for James discovered that it is required. Therefore, I added it back in so that any email that needs to be handled by the error processor gets handled appropriately instead of vanishing into the ether.

Essentially, the way I have this section setup, any SMTP mail received by the server is added to the "data/mail/local" repository. I would be willing to bet that this behaviour was never intended by the system creators, but the flexibility provided by James allows it, which I just absolutely adore! To provide a little more insight (and completely exhaust what little I know or assume about the product), the "root" processor is required and seems to be the equivalent of "main" in a "C" program. That is, it is expected to be present and receives control of the mail before all other processors. The default configuration used the root processor as a sort of dispatch and controller for the other processors. Rather than doing any processing (to include actual "real" delivery of any mail) I have requested that the mail spool just dump all received email to the specified repository, do not pass go, do not collect two hundred dollars, so to speak.

Again the only reason I have also included the error processor (as it is never explicitly called by the "root" or any other processor) is because the documentation says it is required (although it does not seem to be required by the DTD or else no "one" ever complained about it, although notepad++ did complain about other DTD validation problems as I was trying to strip out as much as I could from the configuration except the bare minimum of what I needed). Also, it seems that if there is an unexpected error that the system expects to find the error processor and if not present (to let the system know what to do), it appears the email vanishes into the "ether", so the error processor essentially requests the same as the root processor, that is, to write the email to the file system (obviously in a different directory so that we can distinguish if an error happened, without having to consult the log files every time).

<dnsserver />

    <dnsserver>
        <servers>
        <server>127.0.0.1</server>
        </servers>
        <autodiscover>false</autodiscover>
        <authoritative>false</authoritative>
        <maxcachesize>50000</maxcachesize>
    </dnsserver>

Minimal changes were made to this section, and as stated previously, this configuration is probably not very "kosher", but met my needs. I set the DNS server "/dnsserver/servers/server" value to "localhost" even though I am not running a DNS server on the local host. Again, since I never wanted the server to even attempt to send email outside of the local system, I figured that if I never could actually find the IP address of any server that this would be an additional guarantee. I also ensured that the value of "auto discover" was set to false so that the system didn't try to find realistic values (as I wanted it to use my obviously unrealistic ones). I don't believe I had to change the value of "authoritative", but it shouldn't make much difference to this specific configuration either way, since the system will never get a response to a DNS query from the local host anyway (at least as long as I don't install a DNS server on my Windows Vista box for some silly reason).

<remotemanager /> <pop3server />

   <remotemanager enabled="false" />

    <pop3server enabled="false" />

I certainly didn't want a POP3 service listening on my local machine, so as it shows in the config, it is disabled. The comments in the out of the box version of the config file seemed to indicate that some amount of resources would be consumed if the "handler" subsections remained, even with the service disabled, so that is the reason for the empty section. I never really found clarity on if that meant I should remove these sections altogether (even the empty sections), but as I had some trouble with removing other sections, I felt pretty safe with disabling them and removing the bodies. I am sure a few minutes with the documentation would clarify the situation and bring this question to resolution.

</smtpserver>

    <smtpserver enabled="true">
        <port>25</port>
        <bind>127.0.0.1</bind>
        <handler>
            <helloName autodetect="false">localhost</helloName>
            <connectiontimeout>360000</connectiontimeout>
            <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
            <maxmessagesize>0</maxmessagesize>
        </handler>
    </smtpserver>

I made only minimal changes to this section from the out of the box version other than stripping comments, but there are a few items of particular interest in this section. First, I requested that the server bind to the local host address only "127.0.0.1". As this is (generally regarded as) non-routeable, it means that nothing other than services running on the local machine should be able to connect to it and that it should not be able to connect to anything outside of the local machine.

I also asked the server not to auto detect the "helloname". This is the name that an SMTP server uses when it first initiates a conversation with another SMTP server. Given the other configuration changes/limitations, this was probably not necessary, but I did it anyway. I also set the hello name to "localhost" explicitly.

I probably could have restricted the authorized addresses down to "127.0.0.1/32" allowing only local host, but as remember (at least as I recall) this entire address block is non-routeable, therefore the server should never see an actual address in this block, we should be OK. If I had of thought of it (and I might make the change anyway) I would have ratcheted it down to "127.0.0.1/32", but it slipped my attention when I was setting the initial configuration.

<nntpserver />

    <nntpserver enabled="false" />

Since I did not need or want an NNTP server, I disabled this section and removed the body.

<nntp-repository />

    <nntp-repository>
        <readOnly>false</readOnly>

        <rootPath>file://data/nntp/groups</rootPath>
        <tempPath>file://data/nntp/temp</tempPath>
        <articleIDPath>file://data/nntp/articleid</articleIDPath>
        <articleIDDomainSuffix>news.james.apache.org</articleIDDomainSuffix>

        <newsgroups>
            <newsgroup>org.apache.james.dev</newsgroup>
            <newsgroup>org.apache.james.user</newsgroup>
            <newsgroup>org.apache.avalon.dev</newsgroup>
            <newsgroup>org.apache.avalon.user</newsgroup>
        </newsgroups>

        <spool>
            <configuration>
                <spoolPath>file://data/nntp/spool</spoolPath>
                <threadCount>0</threadCount>
                <threadIdleTime>60000</threadIdleTime>
            </configuration>
        </spool>
    </nntp-repository>

This is another section where I could have probably dome some more "trimming", possibly even removing the entire body of the "nntp-repository" tag; however, since I had already disabled the NNTP server I didn't feel this was necessary. A little more time with the project documentation and you will probably be able to put the question to rest; however, I don't see how it could hurt. The one item that did give me pause about removing the whole section was a recommendation in the comments of the out of the box configuration suggesting that if you disable the NNTP server that you also set the "threadcount" to "zero" in this section otherwise you will have a thread started to poll for NNTP spool items. I wasn't sure if I removed this section if the "threadcount" would be defaulted to zero or not, so I figured I would just change the value of the tag/parameter and leave it at that.

Besides by the time I got to this section (I spent the most time in the "spoolmanager" section), I was starting to tire (it was after mid-night) and didn't think there was much impact either way (other than the aforementioned warning that I attempted to heed).

<spoolrepository />

    <spoolrepository destinationURL="file://data/mail/spool/" type="SPOOL"/>

Other than changing the destination URL, which I wouldn't have done if I had known the relative nature of this path as reflected upon earlier, I made no changes to this value with respect to the out of the box configuration.

</mailstore>

    <mailstore>
        <repositories>

            <repository class="org.apache.james.mailrepository.AvalonMailRepository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>MAIL</type>
                </types>
            </repository>

            <repository class="org.apache.james.mailrepository.AvalonSpoolRepository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>SPOOL</type>
                </types>
            </repository>

            <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Object_Repository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>OBJECT</type>
                </types>
                <models>
                    <model>SYNCHRONOUS</model>
                    <model>ASYNCHRONOUS</model>
                    <model>CACHE</model>
                </models>
            </repository>

            <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Stream_Repository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>STREAM</type>
                </types>
                <models>
                    <model>SYNCHRONOUS</model>
                    <model>ASYNCHRONOUS</model>
                    <model>CACHE</model>
                </models>
            </repository>

        </repositories>
    </mailstore>

My changes to this section were to remove unneeded persistence mechanisms. Since I only needed to have the mail deposited onto the file system, I removed all of the database and JDBC repositories. I probably could have removed even the "filepair" repositories, but I thought I might want to explore their use at some point, so I left them in.

Remaining Sections

    <users-store>
        <repository name="LocalUsers" class="org.apache.james.userrepository.UsersFileRepository">
            <destination URL="file://data/mail/users/"/>
        </repository>
    </users-store>

    <database-connections />

    <connections>
        <idle-timeout>300000</idle-timeout>
        <max-connections>30</max-connections>
    </connections>

    <sockets>
        <server-sockets>
            <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultServerSocketFactory"/>
        </server-sockets>

        <client-sockets>
            <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultSocketFactory"/>
        </client-sockets>
    </sockets>

    <thread-manager>
        <thread-group>
            <name>default</name>
            <priority>5</priority>
            <is-daemon>false</is-daemon>
            <max-threads>20</max-threads>
            <min-threads>5</min-threads>
            <min-spare-threads>5</min-spare-threads>
        </thread-group>
    </thread-manager>
                

The remaining sections have few changes and as such, I will provide this information in a single section rather than having a number of relatively small sections. In the "users-store" section, my only change was to change the "var" to "data", for the misguided reasons stated earlier. This change is not necessarily needed, and should only be made for consistency or if your requirements vary and you want the user store in a different location from the other persisted items.

Since I am not really using a "user-store" as I am dumping all mail to a directory on the file system, it may be possible to also remove the user-store section, but more exploration will be needed before confirming this change.

Since I am not using any database connections or persistence, I removed the body of the "database-connections" section of the configuration file.

I made no changes to the <sockets> section of the configuration as it did not seem to be required.

The only changes to the thread-manager section were to reduce the number of resources consumed by the server, since it will only be used for development purposes and will never be utilized for "real world" email server purposes (at least not with this configuration). The number of threads could probably be further reduced, but these seemed reasonable to me and had little impact overall on system resource while the server was running.

Further Optimizations

After having done my initial work with James, additional requirements indicated that we needed to be able to leverage an email system in order to allow our users to interact with the system in a manner other than just through the web application. Specifically, since we were already sending out email notifications, the users requested the ability to respond to these notifications and have those emails posted on the site as if the user had visited the web site. Based on my familiarity with James, I suggested that the "mailet" capability of James would provide an excellent means of accomplishing this goal.

Other options were to use more traditional mail systems and using "pipes", but I still consider James' capabilities with regard to "mailets" superior to standard piping techniques. I also know that other systems allow for "mailets" and that this was not necessarily unique to James, but since I was already using James and familiar with it, I decided to explore this further. Long story short, in the course of developing the initial mailet, I was able to do further investigation into the configuration of James and discovered a number of additional changes that further optimized James for my use. As a matter for another article, I was significantly impressed with the mailet API exposed by James the simplicity of creating a custom mailet for our needs. I will probably look to document this experience and implementation in a further article on James.

In order to make more significant changes to the James configuration file, it is necessary to broaden our scope to include the "assembly.xml" file that resides in the same directory as the "config.xml" file that we discussed earlier ([install location]/apps/james/SAR-INF/assembly.xml). In order for the below file to work, you must also make changes to assembly.xml.

config.xml

The following is my "config.xml" with additional tweaks. An explanation of the changes follows the code listing:

<?xml version="1.0"?>
<!DOCTYPE config [
<!ENTITY listserverConfig SYSTEM "../conf/james-listmanager.xml">
<!ENTITY listserverStores SYSTEM "../conf/james-liststores.xml">
<!ENTITY fetchmailConfig SYSTEM "../conf/james-fetchmail.xml">
<!ENTITY smtphandlerchainConfig SYSTEM "../conf/james-smtphandlerchain.xml">
]>

<config>
    <James>
        <postmaster>Postmaster@localhost</postmaster>
        <servernames autodetect="false" autodetectIP="false">
            <servername>localhost</servername>
        </servernames>
        <usernames ignoreCase="true" enableAliases="true" enableForwarding="false"/>
        <inboxRepository>
            <repository destinationURL="file://data/mail/inboxes/" type="MAIL"/>
        </inboxRepository>
    </James>

    <mailetpackages>
        <mailetpackage>org.apache.james.transport.mailets</mailetpackage>
        <mailetpackage>org.apache.james.transport.mailets.smime</mailetpackage>
    </mailetpackages>

    <matcherpackages>
        <matcherpackage>org.apache.james.transport.matchers</matcherpackage>
        <matcherpackage>org.apache.james.transport.matchers.smime</matcherpackage>
    </matcherpackages>

    <spoolmanager>
        <threads> 5 </threads>

        <processor name="root">
            <mailet match="All" class="ContentMailet" />
            <mailet match="All" class="ToRepository">
                <repositoryPath>file://data/mail/local/</repositoryPath>
            </mailet>
        </processor>

      <processor name="error">
         <mailet match="All" class="ToRepository">
            <repositoryPath> file://data/mail/error/</repositoryPath>
         </mailet>
      </processor>
    </spoolmanager>

    <dnsserver>
        <servers>
          <server>127.0.0.1</server>
        </servers>
        <autodiscover>false</autodiscover>
        <authoritative>false</authoritative>
        <maxcachesize>50000</maxcachesize>
    </dnsserver>

   <remotemanager enabled="true">
      <port>4555</port>
      <bind>127.0.0.1</bind>
      <handler>
         <helloName autodetect="false">myMailServer</helloName>
         <administrator_accounts>
            <account login="root" password="password"/>
         </administrator_accounts>
         <connectiontimeout> 60000 </connectiontimeout>
         <prompt>james&gt;</prompt>
      </handler>
   </remotemanager>


    <smtpserver enabled="true">
        <port>25</port>
        <bind>127.0.0.1</bind>
        <handler>
            <helloName autodetect="false">localhost</helloName>
            <connectiontimeout>360000</connectiontimeout>
            <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
            <maxmessagesize>0</maxmessagesize>
        </handler>
    </smtpserver>

    <spoolrepository destinationURL="file://data/mail/spool/" type="SPOOL"/>

    <mailstore>
        <repositories>

            <repository class="org.apache.james.mailrepository.AvalonMailRepository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>MAIL</type>
                </types>
            </repository>

            <repository class="org.apache.james.mailrepository.AvalonSpoolRepository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>SPOOL</type>
                </types>
            </repository>

            <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Object_Repository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>OBJECT</type>
                </types>
                <models>
                    <model>SYNCHRONOUS</model>
                    <model>ASYNCHRONOUS</model>
                    <model>CACHE</model>
                </models>
            </repository>

            <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Stream_Repository">
                <protocols>
                    <protocol>file</protocol>
                </protocols>
                <types>
                    <type>STREAM</type>
                </types>
                <models>
                    <model>SYNCHRONOUS</model>
                    <model>ASYNCHRONOUS</model>
                    <model>CACHE</model>
                </models>
            </repository>

        </repositories>
    </mailstore>

    <users-store>
        <repository name="LocalUsers" class="org.apache.james.userrepository.UsersFileRepository">
            <destination URL="file://data/mail/users/"/>
        </repository>
    </users-store>

    <connections>
        <idle-timeout>300000</idle-timeout>
        <max-connections>30</max-connections>
    </connections>

    <sockets>
        <server-sockets>
            <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultServerSocketFactory"/>
        </server-sockets>

        <client-sockets>
            <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultSocketFactory"/>
        </client-sockets>
    </sockets>

    <thread-manager>
        <thread-group>
            <name>default</name>
            <priority>5</priority>
            <is-daemon>false</is-daemon>
            <max-threads>20</max-threads>
            <min-threads>5</min-threads>
            <min-spare-threads>5</min-spare-threads>
        </thread-group>
    </thread-manager>
</config>

Essentially, all that I did was that I was able to remove the sections that I was not using, such as the NNTP server. However, you cannot remove these sections from the config file without also removing their corresponding application blocks from the assembly.xml file. My revised assembly.xml is provided in the following section.

One additional item of note is that I had originally removed the "error" processor from the spoolmanager section of the configuration. While the system will allow you to do this, any mail that results in an error (address, if using a custom mailet and it throws and exception) however, will not necessarily be logged to the file system and you will end up with some interesting error messages in the James log files. My recommendation is to leave the error processor section in the configuration. This is also in line with the James configuration documentation in that the "error" processor is required for proper James configuration.

assembly.xml

In order to remove the unneeded configuration options from the "config.xml" file above, their corresponding application block entries from the assembly.xml file must be removed. The following is my revised assembly.xml file with the unneeded applications removed:

<?xml version="1.0"?>

<assembly>

  <!-- The James block  -->
  <block name="James" class="org.apache.james.James" >

    <!-- Specify which components will provide the services required by this
    block. The roles are specified in the code and the .xinfo file. The names
    here must match the names specified for a Block in this xml file.   -->
    <provide name="dnsserver" role="org.apache.james.services.DNSServer"/>
    <provide name="mailstore" role="org.apache.avalon.cornerstone.services.store.Store"/>
    <provide name="users-store" role="org.apache.james.services.UsersStore"/>
    <provide name="localusersrepository" role="org.apache.james.services.UsersRepository"/>
    <provide name="spoolrepository" role="org.apache.james.services.SpoolRepository"/>
    <provide name="sockets"
             role="org.apache.avalon.cornerstone.services.sockets.SocketManager"/>
    <provide name="scheduler"
             role="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler"/>
    <provide name="database-connections"
             role="org.apache.avalon.cornerstone.services.datasources.DataSourceSelector" />
  </block>

  <!-- The James Spool Manager block  -->
  <block name="spoolmanager" class="org.apache.james.transport.JamesSpoolManager" >
    <provide name="spoolrepository" role="org.apache.james.services.SpoolRepository"/>
    <provide name="matcherpackages" role="org.apache.james.services.MatcherLoader"/>
    <provide name="mailetpackages" role="org.apache.james.services.MailetLoader"/>
  </block>

  <block name="matcherpackages" class="org.apache.james.transport.JamesMatcherLoader" >
    <provide name="James" role="org.apache.mailet.MailetContext"/>
  </block>

  <block name="mailetpackages" class="org.apache.james.transport.JamesMailetLoader" >
    <provide name="James" role="org.apache.mailet.MailetContext"/>
  </block>

  <block name="dnsserver" class="org.apache.james.dnsserver.DNSServer" />

  <block name="remotemanager" class="org.apache.james.remotemanager.RemoteManager" >
    <provide name="users-store" role="org.apache.james.services.UsersStore"/>
    <provide name="localusersrepository" role="org.apache.james.services.UsersRepository"/>
    <provide name="sockets"
             role="org.apache.avalon.cornerstone.services.sockets.SocketManager"/>
    <provide name="connections"
             role="org.apache.james.services.JamesConnectionManager"/>
    <provide name="James" role="org.apache.james.services.MailServer"/>
    <provide name="thread-manager"
             role="org.apache.avalon.cornerstone.services.threads.ThreadManager" />
  </block>

  <!-- SMTP Server -->
  <block name="smtpserver" class="org.apache.james.smtpserver.SMTPServer" >
    <provide name="James" role="org.apache.mailet.MailetContext"/>
    <provide name="localusersrepository" role="org.apache.james.services.UsersRepository"/>
    <provide name="dnsserver" role="org.apache.james.services.DNSServer"/>
    <provide name="sockets"
             role="org.apache.avalon.cornerstone.services.sockets.SocketManager"/>
    <provide name="connections"
             role="org.apache.james.services.JamesConnectionManager"/>
    <provide name="James" role="org.apache.james.services.MailServer"/>
    <provide name="thread-manager"
             role="org.apache.avalon.cornerstone.services.threads.ThreadManager" />
  </block>

  <!-- The High Level Storage block -->
  <block name="mailstore" class="org.apache.james.core.AvalonMailStore" >
    <provide name="database-connections"
             role="org.apache.avalon.cornerstone.services.datasources.DataSourceSelector" />
  </block>

  <!-- The main SpoolRepository -->
  <block name="spoolrepository" class="org.apache.james.mailrepository.MailStoreSpoolRepository" >
    <provide name="mailstore"
             role="org.apache.avalon.cornerstone.services.store.Store" />
  </block>

  <!-- The User Storage block -->
  <block name="users-store" class="org.apache.james.core.AvalonUsersStore" >
    <!-- Configure file based user store here, defaults should be fine -->
    <provide name="mailstore"
             role="org.apache.avalon.cornerstone.services.store.Store"/>
    <provide name="database-connections"
             role="org.apache.avalon.cornerstone.services.datasources.DataSourceSelector" />
  </block>

  <!-- This is needed to link the smtpserver to the local user repository -->
  <block name="localusersrepository" class="org.apache.james.core.LocalUsersRepository">
    <provide name="users-store"
             role="org.apache.james.services.UsersStore"/>
  </block>

  <!-- Configuration for Cornerstone Blocks only after here
       NOTHING BELOW THIS SHOULD NEED CHANGING,
       (unless you want secure sockets (TLS)) -->

  <!-- The Connection Manager block -->
  <block name="connections"
         class="org.apache.james.util.connection.SimpleConnectionManager" >
    <provide name="thread-manager"
             role="org.apache.avalon.cornerstone.services.threads.ThreadManager" />
  </block>

  <!-- The Socket Manager block -->
  <block name="sockets"
         class="org.apache.avalon.cornerstone.blocks.sockets.DefaultSocketManager"/>

  <!-- The Time Scheduler block -->
  <block name="scheduler"
         class="org.apache.avalon.cornerstone.blocks.scheduler.DefaultTimeScheduler" >
    <provide name="thread-manager"
             role="org.apache.avalon.cornerstone.services.threads.ThreadManager" />
  </block>

  <!-- The DataSourceSelector block -->
  <block name="database-connections"
         class="org.apache.avalon.cornerstone.blocks.datasources.DefaultDataSourceSelector" />

  <!-- The ThreadManager block -->
  <block name="thread-manager"
         class="org.apache.avalon.cornerstone.blocks.threads.DefaultThreadManager" />
</assembly>

The changes can be seen by doing a diff/compare between the two files, but simply put, I removed the following sections/blocks:

  • pop3server
  • nntpserver
  • nntp-repository
  • fetchmail

These correspond to the same sections we removed from the config.xml file specified earlier. Other than the removal of these sections, I made no other changes to the assembly.xml file.

environment.xml - Customizing log files

In addition to the changes above for optimization of James for use as a development or personal developer email server, I also found the following changes to the logging configuration useful while doing my development. The default behaviour of the logging setup for James creates a new log file every time the server is started. While this is potentially interesting for a production server, it makes it difficult for a developer to have a "tail -f" on a file open so that you can be watching the log files as you develop/test. Therefore, I changed the logging behaviour by modifying the environment.xml file located in the same directory as the config.xml and assembly.xml.

Additionally, I removed the loggers for the applications sections that I removed above. Please see the list above for the names and you can remove these or leave these as you desire. The only side effect of leaving these entries is that a file is created in the log file directory, which should never receive any log messages, as we don't have these blocks/apps/subsystems enabled. If you don't want to even be bothered by these log files, remove the sections from the <categories> section of the environment.xml. You will also want to remove the <file> configuration sections from the <targets> section of the configuration that also correspond to the identical sections removed from the config file as enumerated in the list above.

Simply put, I removed the <rotation> section from each <file> section of the configuration within the <targets> section of the XML config file. I also changed the value of the <append> section from true to false, which essentially keeps my log files trimmed to only the most recent messages, which keeps me from having to wade through large log files or manually clearing the log files on a regular basis. I would not suggest you to do this on a production system, but as I am only using this configuration for testing at present, I found these changes convenient.

This configuration (I assume) should be pretty standard and probably comes from the Apache Commons Logging project. After some investigation, I did not find the exact log file format as is used below documented anywhere, so I am not sure exactly what is being used. The dependencies of the James project list the avalon-logkit as a dependency as well as log4j, so if you want more information or want to do additional configuration tuning to the log files, you can review the documentation provided these projects as they may offer a clue as to additional logging configuration.

My logging configuration file with my changes follows:

<?XML version="1.0"?>

<server>

  <classloaders default="default" version="1.0">
    <classloader name="default" parent="*system*">
      <entry location="./SAR-INF/classes" />
      <fileset dir="./SAR-INF/lib">
        <include name="*.jar" />
      </fileset>
    </classloader>
    <predefined name="*system*" />
  </classloaders>

  <logs version="1.1">
    <!-- see http://jakarta.apache.org/avalon/excalibur/logger/index.html -->
    <factories>
      <factory type="file" class="org.apache.avalon.excalibur.logger.factory.FileTargetFactory"/>
    </factories>

    <categories>
      <category name="" log-level="INFO">
        <log-target id-ref="default"/>
      </category>
      <category name="James.Mailet" log-level="INFO">
        <log-target id-ref="James-Mailet-target"/>
      </category>
      <category name="James" log-level="INFO">
        <log-target id-ref="James-target"/>
      </category>
      <category name="spoolmanager" log-level="INFO">
        <log-target id-ref="spoolmanager-target"/>
      </category>
      <category name="dnsserver" log-level="INFO">
        <log-target id-ref="dnsserver-target"/>
      </category>
      <category name="remotemanager" log-level="INFO">
        <log-target id-ref="remotemanager-target"/>
      </category>
      <category name="smtpserver" log-level="INFO">
        <log-target id-ref="smtpserver-target"/>
      </category>
      <category name="mailstore" log-level="INFO">
        <log-target id-ref="mailstore-target"/>
      </category>
      <category name="users-store" log-level="INFO">
        <log-target id-ref="users-store-target"/>
      </category>
      <category name="objectstorage" log-level="INFO">
        <log-target id-ref="objectstorage-target"/>
      </category>
      <category name="connections" log-level="INFO">
        <log-target id-ref="connections-target"/>
      </category>
      <category name="sockets" log-level="INFO">
        <log-target id-ref="sockets-target"/>
      </category>
      <category name="scheduler" log-level="INFO">
        <log-target id-ref="scheduler-target"/>
      </category>
    </categories>

    <targets>
      <file id="default">
        <filename>${app.home}/logs/default.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="James-Mailet-target">
        <filename>${app.home}/logs/mailet.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="James-target">
        <filename>${app.home}/logs/james.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="spoolmanager-target">
        <filename>${app.home}/logs/spoolmanager.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="dnsserver-target">
        <filename>${app.home}/logs/dnsserver.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="remotemanager-target">
        <filename>${app.home}/logs/remotemanager.log</filename>
        <append>false</append>
      </file>
      <file id="smtpserver-target">
        <filename>${app.home}/logs/smtpserver.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="mailstore-target">
        <filename>${app.home}/logs/mailstore.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="users-store-target">
        <filename>${app.home}/logs/usersstore.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="objectstorage-target">
        <filename>${app.home}/logs/objectstore.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="connections-target">
        <filename>${app.home}/logs/connections.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="sockets-target">
        <filename>${app.home}/logs/sockets.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
      <file id="scheduler-target">
        <filename>${app.home}/logs/scheduler.log</filename>
        <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}: %{message}\n%{throwable}</format>
        <append>false</append>
      </file>
    </targets>
  </logs>
</server>

Conclusion

As stated early in this document, James is a very powerful mail platform. After my initial use of James as a simple SMTP server for development and testing of email capabilities, I have expanded my use of James to include the use of custom mailets that add functionality to the systems we are developing. I would consider James a very strong mail "platform" from which to build or include functionality into any email system. It also appears that others are using James pretty much "out of the box" as enterprise class SMTP as well as other services. I would highly encourage anyone looking to implement custom email capabilities to consider James and at least validate whether it would be of interest to your efforts.

I will most likely develop additional documentation on the use of James for the purpose of an email platform upon which to develop email or email like capabilities and/or interfaces to existing or new applications. For instance, one implementation of James in our development environment provides for James integration into a Sendmail solution with custom mailets and configuration for both James and Sendmail that might prove of interest to other technology professionals.

Good luck in your use of James as it is a very exciting platform and provides both simplicity of use and development as well as very sophisticated email and server functionality.

by Lance Hendrix

version 68