Reading Mail with POP3






Reading Mail with POP3

Credit: John Wells

Problem

You want to connect to an POP server in order to read and download the messages stored there.

Solution

The net/pop.rb package, written by Minero Aoki, is part of Ruby's standard library, and provides a foundation on which to build a POP (Post Office Protocol)-oriented email application. As with the previous recipe on IMAP, we'll walk through some common ways of accessing a mail server with the POP API.

For this recipe, we assume you have access to a POP3 server running at mail.myhost. com on the standard POP3 port 110. Just as in the previous IMAP example, your username is "username", and password is (yep) "password".

To make the initial connection to the server, it's as simple as:

	require 'net/pop'

	conn = Net::POP3.new('mail.myhost.com')
	conn.start('username', 'password')

If you receive no errors, you've got an open session to your POP3 server, and can use the conn object to communicate with the server.

The following code acts like a typical POP3 client: having connected to the server, it downloads all the new messages, and then deletes them from the server. The deletion is commented out so you don't lose mail accidentally while testing this code:

	require 'net/pop'

	conn = Net::POP3.new('mail.myhost.com')
	conn.start('username', 'password')

	conn.mails.each do |msg|
	   File.open(msg.uidl, 'w') { |f| f.write msg.pop }
	   # msg.delete
	end

	conn.finish

Discussion

POP3 is a much simpler protocol than IMAP, and arguably a less powerful one. It doesn't support the concept of folders, so there's no need to start off by selecting a particular folder (like we did in the IMAP recipe). Once you start a session, you have immediate access to all messages currently retained on the server.

IMAP stores your folders and your messages on the server itself. This way you can access the same messages and the same folders from different clients on different machines. For example, you might go to work and access an IMAP folder with Mozilla Thunderbird, then go home and access the same folder with a web-based mail client.

With POP3, there are no server-side folders. You're supposed to archive your messages on the client side. If you use a POP3 client to download messages at work, when you get home you won't be able to access those messages. They're on your work computer, not on the POP3 server.

IMAP assigns a unique, unchanging ID to each message in the mailbox. By contrast, when you start a POP3 session, POP3 gives each message a "sequence number" reflecting its position in the mailbox at that time. The next time you connect to the POP3 server, the same message may have a different sequence number, as new, incoming messages can affect the sequencing. This is why POP3 clients typically download messages immediately and delete them from the server.

If we want to go outside this basic pattern, and leave the messages on the server, how can we keep track of messages from one connection to another? POP3 does provide a unique string ID for each message: a Unique Identification Listing, or UIDL. You can use a UIDL (which persists across POP3 sessions) to get a sequence number (which doesn't) and retrieve a message across separate connections.

This code finds the IDs of email messages from a particular source:

	conn = Net::POP3.new('mail.myhost.com')
	conn.start('username', 'password')
	ids = conn.mails.collect {|msg| msg.uidl if msg.pop.match('jabba')}
	conn.finish
	# => ["UID2-1141260595", "UID3-1141260595"]

Now we have unique identifiers for each of our matching messages. Given these, we can start a new POP3 session and use these UIDLs to retrieve each message individually:

	conn2 = Net::POP3.new('mail.myhost.com')
	conn.start('username', 'password')

	conn.each_mail {|msg| puts msg.pop if msg.uidl=='UID3-1141260595'}

	conn.finish
	# Return-Path: <[email protected]>
	# X-Original-To: [email protected]
	# Delivered-To: [email protected]
	# …

Here we call the method Net::POP3#each_mail to iterate over all the messages in the mailbox. Each message is passed into the code block as a Net::POPMail message. We look at each message's UIDL and, when we find the message we want, we call Net::POPMail#pop to print it out.

Forwarding mail to a cell phone

Let's revisit our example from the IMAP recipe. You're waiting for a very important email, and you want to have it forwarded to your cell phone as soon as it comes in. You're able to send mail through a SMTP server hosted on port 25 of the same machine as your POP3 server. The email address of your cell phone is [email protected]

This program checks your POP3 server for new email every five minutes. If a new message from anyone at huttfoundation.org is found, it forwards the message to your cell phone via SMS.

	#!/usr/bin/env ruby
	# forward_important_messages.rb

	require 'net/pop'
	require 'net/smtp'

	$address = 'huttfoundation.org'
	$from = '[email protected]'
	$to = '[email protected]'
	smtp_server = 'my.mailhost.com'
	pop_server = 'my.mailhost.com'
	username = 'username'
	password = 'password'

	$found = Hash.new

	def send_msg (text)
	  count = 1
	  while(text.size > 0) do
	    # SMS messages limited to 160 characters
	    msg = text.slice!(0, 159)
	    full_msg = "From: #{$from}\n"
	    full_msg += "To: #{$to}\n"
	    full_msg += "Subject: Found message from #{$address} (#{count})!\n"
	    full_msg += "Date: #{Time.now}\n"
	    full_msg += msg + "\n"
	    Net::SMTP.start(smtp_server, 25) do |smtp|
	      smtp.send_message full_msg, $from, $to
	    end
	    count += 1
	  end
	end

	loop do
	  conn = Net:: 
POP3.new(pop_server)
	  conn.start('username', 'password')

	  uidls = conn.mails.collect do |msg|
	    msg.uidl if msg.pop.match(/#{$address}/)
	  end

	  uidls.each do |one_id|
	    if ! $found.has_key? one_id
	      $found[one_id] = true
	      conn.each_mail do |msg|
	        send_msg(msg.uidl) if msg.uidl==one_id
	      end
	    end
	  end
	  conn.finish
	  # Sleep for 5 minutes.
	  sleep (60*60*5)
	end

See Also

  • Recipe 14.6, "Reading Mail with IMAP"

  • RFC1939 describes the POP3 protocol



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows