One of the people in my team ran into a problem yesterday, where a customer could only send data via Email. The customer does not support formal EDI or have IT staffers who can support building or using something more automated. After talking about how the processing needed to be performed 7 days a week, we knew we had to build something automated. I went to my Google and StackOverflow to search for a free IMAP solution (I so wish IMAP/POP/SFTP were supported in the core framework) and found AE.Net.Mail. The product doesn’t have any documentation, but after looking at 2 posts about issues other people were having with with the client, I was able to figure out everything I needed to process my emails and save the attachments out to disk. The project is even support via NuGet, which is a big plus. All of the code below is based on using AE.Net.Mail v1.6.0.0 with .NET 4.0.
Here is the code I built to process email attachments from 2 different customers:
// Connect to Exchange 2007 IMAP server (locally), without SSL.
using (ImapClient ic = new ImapClient("<server address>", "<user account>", "<user password>", ImapClient.AuthMethods.Login, 143, false))
{
// Open a mailbox, case-insensitive
ic.SelectMailbox("INBOX");
// Get messages based on the flag "Undeleted()".
Lazy<MailMessage>[] messages = ic.SearchMessages(SearchCondition.Undeleted(), false);
// Process each message
foreach (Lazy<MailMessage> message in messages)
{
MailMessage m = message.Value;
string sender = m.From.Address;
string fileName = string.Empty;
// Rules by Sender
switch (sender)
{
case "zachary@customerA.com":
fileName = ExtractString(m.Subject, "(", ")");
foreach (Attachment attachment in m.Attachments)
{
attachment.Save(@"C:\Demo\" + fileName + Path.GetExtension(attachment.Filename));
}
break;
case "hunter@customerB.com":
foreach (Attachment attachment in m.Attachments)
{
string filePrefix = attachment.Filename.Substring(0, attachment.Filename.IndexOf('_'));
switch (filePrefix)
{
case "DAILY":
fileName = "CustomerB_Inventory";
break;
case "PULL":
fileName = "CustomerB_Pulls";
break;
case "RECEIPT":
fileName = "CustomerB_Receipts";
break;
}
attachment.Save(@"C:\Demo\" + fileName + Path.GetExtension(attachment.Filename));
}
break;
}
// Delete Message (so it drops out of criteria and won't be double processed)
ic.DeleteMessage(m);
}
}
/// <summary>
/// Helper method to pull out a string between a start and stop string.
/// Example:
/// string story = "The boy and the cat ran to the store.";
/// ExtractString(story, "cat", "to"); //returns "ran"
/// </summary>
/// <param name="stringToParse">String to search</param>
/// <param name="startTag">Starting String Pattern</param>
/// <param name="endTag">Ending String Pattern</param>
/// <returns>String found between the Starting and Ending Pattern.</returns>
static string ExtractString(string stringToParse, string startTag, string endTag)
{
int startIndex = stringToParse.IndexOf(startTag) + startTag.Length;
int endIndex = stringToParse.IndexOf(endTag, startIndex);
return stringToParse.Substring(startIndex, endIndex - startIndex);
}
Most of the processing is done in the switch statement, since I’m going to apply different parsing rules by customer. This is far from a long term elegant and maintainable longterm solution, but it gets the problem solved quick. Here is the rule summary by customer.
case “zachary@customerA.com”:
Everyday I’m going to get 3 emails from this customer, the subject for each email will be:
“Daily Reports for Customer A (CustomerA_Inventory)”
“Daily Reports for Customer A (CustomerA_Receipts)”
“Daily Reports for Customer A (CustomerA_Pulls)”
All three files have a CSV attachment that is called CustomerA.csv. The file in each email contains different data and I need all 3 written to disk for processing in my EDI solution. My solution was to parses the subject using the helper method, and renames each attachment with the name matching in the (parenthesis). The result of processing the emails with the code above, is 3 files written to disk with names that correctly identify their data content.
C:\Demo\CustomerA_Inventory.CSV
C:\Demo\CustomerA_Receipts.CSV
C:\Demo\CustomerA_Pulls.CSV
case “hunter@customerB.com”:
This customer is similar to the top, but I’m only going to get 1 email with 3 attachments that each have a date appended to the file. I need to save each file to disk without a date stamp, so my EDI processor can be setup to pull in the same “file name” everyday. My solution was to parse the attachment file name and rename the file based on the prefix of the attachment name. The results of processing the email is below.
Source Email:
Attachment 1: DAILY_INVENTORY_06_05_2012.XLS
Attachment 2: DAILY_RECEIPT_06_05_2012.XLS
Attachment 3: DAILY_PULL_06_05_2012.XLS
Results of Processing
C:\Demo\CustomerB_Inventory.XLS
C:\Demo\CustomerB_Pulls.XLS
C:\Demo\CustomerB_Receipts.XLS
Note: Awhile back I looked to see if there was a turn-key app/solution that could pull in emails for processing (basically run as a service on a server, and provide a nice GUI to allow non-technical people to setup rules for email processing), but I couldn’t find anything that worked with our setup. This would ideally be a better solution, since it would allow users to add new rules for new emails at a moments notice, versus my hard-code logic used above.
#1 by jonathan on December 28, 2013 - 3:55 am
hi,
Great article just what I was looking for, really helpful.
I have been trying to get this to work using version 1.7 of ae.net.mail but there seems to be some issue of missing files when trying to debug. I have added the ae.net.mail nuget package to my console application successfully. Its just when debugging i get lots of missing .cs files which is weird (imapclient.cs, textclient.cs) surely these are in the dll i am referencing.??
Any ideas
thanks
#2 by Julius on June 8, 2013 - 10:03 am
Hi Zach,
Your blog is very helpful in understanding AE.NET.Mail.dll
I would like to understand this , some classes are missing, e.g. “Lazy ” can you help me with the source code or explanation.
Kind regards
#3 by Zach on June 18, 2013 - 8:13 am
The Lazy Class provides support for lazy initialization, this is available in .NET Framework 4+.
#4 by Benjamin on August 16, 2012 - 3:21 pm
Thank you for documenting this. After looking around I’ve decided to try using your example to get started. I will be connecting to gmail. Too bad there is no Linq2imap built into .NET.
What version of AE.Net.Mail did you use for this example?
#5 by Zach on August 20, 2012 - 10:39 am
I’m using version 1.6.0.0 with .NET 4.0.