<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://devlicio.us/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Rob Reynolds - The Fervent Coder : SidePOP, HowTo</title><link>http://devlicio.us/blogs/rob_reynolds/archive/tags/SidePOP/HowTo/default.aspx</link><description>Tags: SidePOP, HowTo</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP1 (Build: 31106.3070)</generator><item><title>Lessons In Building An Email Parser</title><link>http://devlicio.us/blogs/rob_reynolds/archive/2009/12/18/lessons-in-building-an-email-parser.aspx</link><pubDate>Fri, 18 Dec 2009 09:11:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:54627</guid><dc:creator>Rob Reynolds</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://devlicio.us/blogs/rob_reynolds/rsscomments.aspx?PostID=54627</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://devlicio.us/blogs/rob_reynolds/commentapi.aspx?PostID=54627</wfw:comment><comments>http://devlicio.us/blogs/rob_reynolds/archive/2009/12/18/lessons-in-building-an-email-parser.aspx#comments</comments><description>&lt;h4&gt;&lt;span style="text-decoration:underline;"&gt;&lt;strong&gt;The Classic Infinite Email Loop&lt;/strong&gt;&lt;/span&gt; &lt;/h4&gt;
&lt;p&gt;When building an email parser, one must think about validating an email address that sends you a message. Why? Just because an email message has a from address does not already mean that it is a valid email address. When you&amp;#39;re building an auto-responder, not validating an email can cause an email loop. Some of you can already see where I&amp;rsquo;m going with this.&lt;/p&gt;
&lt;p&gt;I was building something in &lt;a target="_blank" href="http://bombali.googlecode.com"&gt;Bombali&lt;/a&gt; that would respond to emails when receiving them. When Bombali received a message from an address, Bombali would send a response. No checking on the address. In&amp;nbsp;the world of&amp;nbsp;computers, this is&amp;nbsp;a classic mistake.&amp;nbsp;In my head I hear the word &amp;quot;classic&amp;quot; as if it came from SNL and&amp;nbsp;the &amp;quot;Classic Peg&amp;quot; skits. Classssssss-ic (with laughing and shoulder shrugging) Mistake. Right? Well this is all new to me, I&amp;rsquo;ve never been able to do this programmatically until recently. So Bombali received a message with a no reply from address. It sent a response. Then it got a response from the postmaster saying that this was an invalid address. I bet you&amp;rsquo;re guessing what happens next? Yes, Bombali responded to the postmaster. And then the postmaster responded back. Over a matter of about 30 minutes there was quite a bit of traffic back and forth until I deleted the emails before Bombali could check them. At least I was smart enough to set the email checking to every half minute. &lt;/p&gt;
&lt;p&gt;How did I get past this? Validation. An authorized list form of validation. Interested? Read on dear reader. &lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;SidePOP XmlConfigurator&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;I was exposing my configuration in the last &lt;a target="_blank" href="http://devlicio.us/blogs/rob_reynolds/archive/2009/12/08/how-to-check-email-programmatically-sidepop.aspx"&gt;post&lt;/a&gt;, so I built an &lt;a target="_blank" href="http://code.google.com/p/sidepop/source/browse/trunk/product/sidepop/runners/SidePopXmlConfigurator.cs"&gt;XmlConfigurator&lt;/a&gt; that does this for you and gives you back a list of Email Watchers (renamed SidePOPRunner).&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;private void configure_mail_watcher()
{
    EmailWatcherConfigurator configurator = new SidePopXmlConfigurator();
    foreach (EmailWatcher emailWatcher in configurator.configure())
    {
        emailWatcher.MessagesReceived += runner_messages_received;
        emailWatcher.start();
    }
}&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Subscriber for Receiving Email&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The code above and the code below show how easy it is for your application to receive email. I map the email to a local object and then I pass that off to a function to parse and send a response.&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;private void runner_messages_received(object sender, MessageListEventArgs e)
{
    IEnumerable&amp;lt;SidePOPMailMessage&amp;gt; messages = e.Messages;

    foreach (SidePOPMailMessage message in messages)
    {
        Email mail_message = Map.from(message).to&amp;lt;Email&amp;gt;();
        parse_and_send_response(mail_message);
    }
}&lt;/pre&gt;
&lt;p&gt;So now I am receiving email, but I need to parse it do determine what to do. So let&amp;rsquo;s show the parser first before the method for sending a response.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Really Simple Email Parser&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;This is a really simple parser. It&amp;rsquo;s not by any means what some people would use in production and that&amp;rsquo;s fine. It works for what Bombali needs. &lt;/p&gt;
&lt;pre name="code" class="c#"&gt;public MailQueryType parse(Email message, IList&amp;lt;IMonitor&amp;gt; monitors, IDictionary&amp;lt;string, ApprovalType&amp;gt; authorization_dictionary)
{
    MailQueryType query_type = MailQueryType.Authorizing;

    string user = message.from_address.to_lower();
    bool user_is_authorized = false;

    if (authorization_dictionary.ContainsKey(user))
    {
        ApprovalType user_is_approved = authorization_dictionary[user];
        if (user_is_approved == ApprovalType.Approved)
        {
            user_is_authorized = true;
        }
    }

    if (user_is_authorized)
    {
        query_type = MailQueryType.Help;
        string subject_and_body = message.subject + &amp;quot;|&amp;quot; + message.message_body;

        if (message_contains_status(subject_and_body)) query_type = MailQueryType.Status;
        if (message_contains_config(subject_and_body)) query_type = MailQueryType.Configuration;
        if (message_contains_down(subject_and_body)) query_type = MailQueryType.CurrentDownItems;
        if (message_contains_approve(subject_and_body)) query_type = MailQueryType.Authorized;
        if (message_contains_deny(subject_and_body)) query_type = MailQueryType.Denied;
        if (message_contains_version(subject_and_body)) query_type = MailQueryType.Version;
    }

    return query_type;
}&lt;/pre&gt;
&lt;p&gt;I have a MailQueryType so that I separate what how to respond from actually responding. What you can see is that I&amp;rsquo;m already checking to see if we have an approved user before I set a different MailQueryType. The mail parser just sends back how Bombali should respond to the message. &lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Sending a Response&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In subscribing to receiving email, I passed the message to the method below. The second line of the method shows me passing the message off to the mail parser to find out how to respond.&amp;nbsp; The switch could probably be replaced by a Strategy pattern, but for now it works and it&amp;rsquo;s all I need.&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;private void parse_and_send_response(Email mail_message)
{
    string respond_to = mail_message.from_address.to_lower();
    MailQueryType query_type = mail_processor.parse(mail_message, monitors, authorization_dictionary);
    Log.bound_to(this).Info(&amp;quot;{0} received a message from {1} of type {2}.&amp;quot;, ApplicationParameters.name, respond_to, query_type.ToString());

    string response_text = string.Empty;

    if (query_type == MailQueryType.Authorized || query_type == MailQueryType.Denied)
    {
        string[] body_words = mail_message.message_body.Split(&amp;#39; &amp;#39;);
        foreach (string body_word in body_words)
        {
            if (body_word.Contains(&amp;quot;@&amp;quot;))
            {
                respond_to = body_word.Replace(Environment.NewLine, &amp;quot;&amp;quot;);
                break;
            }
        }
    }

    switch (query_type)
    {
        case MailQueryType.Denied:
            authorization_dictionary.Add(respond_to, ApprovalType.Denied);
            return;
            break;
        case MailQueryType.Authorized:
            authorization_dictionary.Add(respond_to, ApprovalType.Approved);
            response_text = string.Format(&amp;quot;Congratulations - you have been approved!{0}Send &amp;#39;help&amp;#39; for options&amp;quot;, Environment.NewLine);
            break;
        case MailQueryType.Help:
            response_text =
                string.Format(
                    &amp;quot;Options - send one:{0} help - this menu{0} status - up time{0} config - all monitors{0} down - current monitors in error{0} version - current version&amp;quot;,
                    Environment.NewLine);
            break;
        case MailQueryType.Status:
            TimeSpan up_time_current = up_time.Elapsed;
            response_text = string.Format(&amp;quot;{0} has been up and running for {1} days {2} hours {3} minutes and {4} seconds.&amp;quot;, ApplicationParameters.name,
                                          up_time_current.Days, up_time_current.Hours, up_time_current.Minutes, up_time_current.Seconds);
            break;
        case MailQueryType.CurrentDownItems:
            response_text = string.Format(&amp;quot;Services currently down:{0}&amp;quot;, Environment.NewLine);
            foreach (IMonitor monitor in monitors)
            {
                if (monitor.who_to_notify_as_comma_separated_list.to_lower().Contains(respond_to))
                {
                    if (!monitor.status_is_good) response_text += string.Format(&amp;quot;{0}{1}&amp;quot;, monitor.name, Environment.NewLine);
                }
            }
            break;
        case MailQueryType.Authorizing:
            response_text =
                string.Format(&amp;quot;Bombali notified admin to add you to approved list. If you are added, you will receive a response.&amp;quot;);
            break;
        case MailQueryType.Version:
            response_text = string.Format(&amp;quot;Bombali is currently running version {0}.&amp;quot;, Version.get_version());
            break;
        default:
            response_text = string.Format(&amp;quot;{0} has not been implemented yet. Please watch for updates.&amp;quot;, query_type);
            break;
    }

    SendNotification
        .from(BombaliConfiguration.settings.email_from)
        .to(respond_to)
        .with_subject(&amp;quot;Bombali Response&amp;quot;)
        .with_message(response_text)
        .and_use_notification_host(BombaliConfiguration.settings.smtp_host);

    if (query_type == MailQueryType.Authorizing)
    {
        SendNotification
        .from(BombaliConfiguration.settings.email_from)
        .to(BombaliConfiguration.settings.administrator_email)
        .with_subject(&amp;quot;Bombali Request&amp;quot;)
        .with_message(string.Format(&amp;quot;{0} reqests approval. Send approve/deny w/email address. Ex. &amp;#39;deny bob@nowhere.com&amp;#39;&amp;quot;, respond_to))
        .and_use_notification_host(BombaliConfiguration.settings.smtp_host);
    }

}&lt;/pre&gt;
&lt;p&gt;You&amp;rsquo;ll notice that I&amp;rsquo;m authorizing any new email addresses that send an email to Bombali before allowing them access to the application. I do that by having an administrator contact I can send a request for approve or deny to before allowing access. I also send an email back to the sender letting them know they are in the process of being approved. At most I would send one email to a bad address. As administrator, the email can be validated by a human. And I only respond to an authorized list. I could also build in something to validate the email by checking for a response from a post master telling me the address doesn&amp;rsquo;t exist.&lt;/p&gt;
&lt;p&gt;So remember readers - always check your sources. Otherwise you might get in an infinite loop. And that would be bad.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=54627" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/rob_reynolds/archive/tags/HowTo/default.aspx">HowTo</category><category domain="http://devlicio.us/blogs/rob_reynolds/archive/tags/SidePOP/default.aspx">SidePOP</category><category domain="http://devlicio.us/blogs/rob_reynolds/archive/tags/Tools/default.aspx">Tools</category></item><item><title>How To Check Email Programmatically - SidePOP</title><link>http://devlicio.us/blogs/rob_reynolds/archive/2009/12/08/how-to-check-email-programmatically-sidepop.aspx</link><pubDate>Tue, 08 Dec 2009 06:46:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:54347</guid><dc:creator>Rob Reynolds</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://devlicio.us/blogs/rob_reynolds/rsscomments.aspx?PostID=54347</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://devlicio.us/blogs/rob_reynolds/commentapi.aspx?PostID=54347</wfw:comment><comments>http://devlicio.us/blogs/rob_reynolds/archive/2009/12/08/how-to-check-email-programmatically-sidepop.aspx#comments</comments><description>&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Background&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Sending email has long been easy to do with the .NET Framework. There really hasn&amp;rsquo;t been a facility for checking email though. I recently had a need to be able to check email with and send a response with &lt;a target="_blank" href="http://bombali.googlecode.com/"&gt;Bombali&lt;/a&gt; (a monitoring tool). I went searching for examples or a solution I could use. I found a few articles on this from a few years ago including the &lt;a target="_blank" href="http://www.codeproject.com/KB/IP/NetPopMimeClient.aspx"&gt;.NET POP3 MIME Client&lt;/a&gt;.&amp;nbsp; I never found anything that was free and I never found anything that was easy to configure. Those who know me know I have a tinge of the NIH (not invented here), especially when there are no free alternatives. So I created my own based on the articles I had read. And in the spirit of chucknorris, I named it &lt;a target="_blank" href="http://sidepop.googlecode.com/"&gt;SidePOP&lt;/a&gt;! &lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Configuration&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Configuration for receiving email should be easy. Feast your eyes on all that is required for configuration (and for those of you who dislike XML, it&amp;rsquo;s not required &amp;ndash; you can configure all in code):&lt;/p&gt;
&lt;pre name="code" class="xml"&gt;&amp;lt;configSections&amp;gt;
  &amp;lt;section name=&amp;quot;sidepop&amp;quot; type=&amp;quot;sidepop.configuration.SidePOPConfiguration, sidepop&amp;quot;/&amp;gt;
&amp;lt;/configSections&amp;gt;

&amp;lt;!-- 110 is normal POP3 SSL uses port 995--&amp;gt;
&amp;lt;sidepop&amp;gt;
  &amp;lt;accounts&amp;gt;
    &amp;lt;add hostName=&amp;quot;mail.somewhere&amp;quot; userName=&amp;quot;yep&amp;quot; password=&amp;quot;&amp;quot; minutesBetweenChecks=&amp;quot;.1&amp;quot; /&amp;gt;
    &amp;lt;add name=&amp;quot;Number1&amp;quot; description=&amp;quot;Main account&amp;quot; enabled=&amp;quot;true&amp;quot; hostName=&amp;quot;mail.somewhere.net&amp;quot; hostPort=&amp;quot;110&amp;quot; useSSL=&amp;quot;false&amp;quot; userName=&amp;quot;&amp;quot; password=&amp;quot;&amp;quot; minutesBetweenChecks=&amp;quot;1&amp;quot; timeoutInMinutes=&amp;quot;1&amp;quot;  /&amp;gt;
  &amp;lt;/accounts&amp;gt;
&amp;lt;/sidepop&amp;gt;&lt;/pre&gt;
&lt;p&gt;You see there are two items here. The first one has the required items in it. The second one is the entire set of items to configure.&amp;nbsp; Notice that we are asking for a password in plaintext. This is where you might think about encrypting this section somehow.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Code Setup&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Configuring SidePOP to run is a little more verbose that I might like right now. Looking below you see that it&amp;rsquo;s still not that much code. I hope to set up a facility to automatically look at the configuration file and configure SidePOP (for those that like XML). If someone wants to send me a patch as well, that would be awesome.&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;private void configure_mail_watcher()
{
    foreach (AccountConfigurationElement account in SidePOPConfiguration.settings.accounts)
    {
        if (account.enabled)
        {
            SidePopRunner runner = new SidePopRunner(new DefaultPop3Client(account.hostName, account.hostPort,
                                     account.useSSL, account.userName,
                                     account.password), account.minutes_between_checks);
            runner.MessagesReceived += runner_messages_received;
            runner.run();
            Log.bound_to(this).Info(&amp;quot;{0} is configured to watch for messages with user {1} at {2} every {3} minutes.&amp;quot;, ApplicationParameters.name,
                                    account.userName, account.hostName, account.minutes_between_checks);
        }
    }
}

private void runner_messages_received(object sender, MessageListEventArgs e)
{
    IEnumerable&amp;lt;SidePOPMailMessage&amp;gt; messages = e.Messages;

    const string subject = &amp;quot;Bombali Response&amp;quot;;
    TimeSpan up_time_current = up_time.Elapsed;
    string text_message = string.Format(&amp;quot;{0} has been up and running for {1} days {2} hours {3} minutes and {4} seconds.&amp;quot;, ApplicationParameters.name, up_time_current.Days, up_time_current.Hours, up_time_current.Minutes, up_time_current.Seconds);

    foreach (SidePOPMailMessage message in messages)
    {
        Log.bound_to(this).Info(&amp;quot;{0} received a message from {1}. Responding that service is still running.&amp;quot;,ApplicationParameters.name,message.From.Address);
    
        SendNotification.from(BombaliConfiguration.settings.email_from).to(message.From.Address).with_subject(
        subject).with_message(text_message).and_use_notification_host(BombaliConfiguration.settings.smtp_host);
    }
}&lt;/pre&gt;
&lt;p&gt;When I start the service, I call the method configure_mail_watcher(). Because I can have multiple accounts I can check with SidePOP, I am going to loop through the settings and check each one to ensure they are enabled. If so, then I am going to set up a SidePopRunner and subscribe to runner_messages_received (this may not work with multiple accounts, I haven&amp;rsquo;t yet tested that).&amp;nbsp; Then I kick off the runner for that account.&lt;/p&gt;
&lt;p&gt;I could instead subscribe to runner.MessageReceived (instead of runner.Message&lt;strong&gt;s&lt;/strong&gt;Received) for getting an event notification for every message instead of batching it up to a list if I desired.&lt;/p&gt;
&lt;p&gt;When a message is received, I find out how long the service has been up. I didn&amp;rsquo;t show it here, but &lt;em&gt;up_time&lt;/em&gt; is a System.Diagnostics.Stopwatch. In the constructor I start it. Now I just capture how long it has been up. Then for each message, I send a response to the sender of the email address telling them how long the service has been up. To add more sophistication, I could very easily add a rules engine here to process the message based on a list of rules looking for keywords in the email.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all I need to do. Now I&amp;rsquo;m up and ready to rock.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;What It Looks Like&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Here is what it looks like running with Bombali. I send the service a text message. This is what it logs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2009-12-07 23:59:05,875 5 [INFO ] - Bombali received a message from &lt;em&gt;someone @ somewhere.net&lt;/em&gt;. Responding that service is still running. &lt;br /&gt;2009-12-07 23:59:05,875 5 [INFO ] - Sending email to &lt;em&gt;someone @ somewhere.net&lt;/em&gt; with subject &amp;quot;Bombali Response&amp;quot; and message: &lt;br /&gt;Bombali has been up and running for 0 days 0 hours 31 minutes and 22 seconds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is what I get from my phone. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://devlicio.us/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/rob_5F00_reynolds/image_5F00_7E751386.png"&gt;&lt;img border="0" width="364" src="http://devlicio.us/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/rob_5F00_reynolds/image_5F00_thumb_5F00_769CFB17.png" alt="Bombali has been up and running for 0 days 0 hours 31 minutes and 22 seconds." height="484" style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="Bombali has been up and running for 0 days 0 hours 31 minutes and 22 seconds." /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Now I know that my service is still up and running with a simple text. All for the low price of a few lines of code and the sidePOP.dll library.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Conclusion&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a target="_blank" href="http://sidepop.googlecode.com/"&gt;SidePOP&lt;/a&gt; gives me a level of sophistication that I didn&amp;rsquo;t have before. Now that I have access to get email, I could very easily add rules to do different things based on the content of the email received. How often have you wanted your application to check email and just didn&amp;rsquo;t want to pay the price for the products out there that can do this? Now it&amp;rsquo;s completely free with SidePOP.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;&lt;span style="text-decoration:underline;"&gt;Download SidePOP&lt;/span&gt;&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;NOTE: This is still early. Any part of the configuration could change. Be sure you take a look at the sample app to always have the newest configuration. Register any bugs you find here: &lt;a target="_blank" href="http://code.google.com/p/sidepop/issues/list" title="http://code.google.com/p/sidepop/issues/list"&gt;http://code.google.com/p/sidepop/issues/list&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING: SidePOP will delete your email when it checks it. That&amp;rsquo;s how it can be sure it&amp;rsquo;s only dealing with new messages every time. Do not test on an account you care about. You&amp;rsquo;ve been warned.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SVN here: &lt;a target="_blank" href="http://sidepop.googlecode.com/svn/trunk/" title="http://sidepop.googlecode.com/svn/trunk/"&gt;http://sidepop.googlecode.com/svn/trunk/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Downloads here: &lt;a target="_blank" href="http://code.google.com/p/sidepop/downloads/list" title="http://code.google.com/p/sidepop/downloads/list"&gt;http://code.google.com/p/sidepop/downloads/list&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=54347" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/rob_reynolds/archive/tags/HowTo/default.aspx">HowTo</category><category domain="http://devlicio.us/blogs/rob_reynolds/archive/tags/SidePOP/default.aspx">SidePOP</category><category domain="http://devlicio.us/blogs/rob_reynolds/archive/tags/Tools/default.aspx">Tools</category></item></channel></rss>