CrackedRuby logo

CrackedRuby

Net::POP3

Overview

Net::POP3 provides Ruby's implementation of the Post Office Protocol version 3, defined in RFC 1939. The protocol retrieves email messages from mail servers using a simple request-response mechanism. Ruby's Net::POP3 class handles connection establishment, authentication, message enumeration, and retrieval operations.

The implementation supports both plain and secure connections through POP3S, handles various authentication mechanisms including APOP, and manages the stateful nature of POP3 sessions. The protocol operates in distinct phases: authorization, transaction, and update, with Ruby abstracting much of the state management.

require 'net/pop'

# Basic connection and authentication
Net::POP3.start('mail.example.com', 110, 'username', 'password') do |pop|
  pop.mails.each do |mail|
    puts mail.header
    mail.delete
  end
end

Net::POP3 returns Net::POPMail objects representing individual messages. Each mail object provides methods for header inspection, body retrieval, and deletion operations. The class manages connection pooling, automatic reconnection on certain failures, and proper session termination.

# Message count and size information
Net::POP3.start('mail.example.com', 110, 'user', 'pass') do |pop|
  puts "#{pop.mails.length} messages (#{pop.n_bytes} bytes total)"
  
  pop.each_mail do |mail|
    puts "Message #{mail.number}: #{mail.length} bytes"
    content = mail.pop
    puts content[0..100] # First 100 characters
  end
end

The protocol's stateful nature means operations affect server state. Deleted messages remain marked for deletion until the session ends successfully. Connection interruptions or errors during the update phase can result in unexpected message states.

# Headers-only retrieval for efficiency
Net::POP3.start('mail.example.com', 110, 'user', 'pass') do |pop|
  pop.mails.each do |mail|
    header = mail.header
    if header =~ /Subject: .*urgent/i
      puts mail.pop  # Only retrieve urgent messages
      mail.delete
    end
  end
end

Basic Usage

Net::POP3 requires establishing a connection, authenticating, and then performing operations within a session context. The start class method handles connection setup and teardown automatically, executing the provided block within an active session.

require 'net/pop'

# Standard connection with automatic cleanup
Net::POP3.start('mail.server.com', 110, 'username', 'password') do |pop|
  if pop.mails.empty?
    puts "No messages"
  else
    puts "Retrieved #{pop.mails.length} messages"
    
    pop.mails.each_with_index do |mail, index|
      puts "Message #{index + 1}: #{mail.length} bytes"
      puts "From: #{mail.header[/^From: .*/]}"
      puts "Subject: #{mail.header[/^Subject: .*/]}"
      puts "---"
    end
  end
end

Manual connection management provides more control over the session lifecycle. This approach requires explicit connection establishment, authentication, and cleanup operations.

# Manual connection management
pop = Net::POP3.new('mail.server.com', 110)
begin
  pop.start('username', 'password')
  
  # Check for messages
  message_count = pop.n_mails
  total_size = pop.n_bytes
  puts "#{message_count} messages, #{total_size} bytes total"
  
  # Process messages
  pop.mails.each do |mail|
    content = mail.pop
    # Process message content
    puts "Processing message #{mail.number}"
    
    # Mark for deletion
    mail.delete
  end
  
ensure
  pop.finish if pop.started?
end

Message retrieval operates through Net::POPMail objects. Each object represents a single message on the server and provides methods for content access and management operations.

Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
  pop.mails.each do |mail|
    # Get message size before retrieval
    size_bytes = mail.length
    puts "Message size: #{size_bytes} bytes"
    
    # Retrieve headers only for filtering
    headers = mail.header
    subject = headers[/^Subject: (.*)$/m, 1]
    from = headers[/^From: (.*)$/m, 1]
    
    # Conditional retrieval based on headers
    if subject && subject.include?('Important')
      full_content = mail.pop
      puts "Retrieved important message from #{from}"
      
      # Message deletion
      mail.delete
      puts "Message marked for deletion"
    end
  end
end

The each_mail method provides an alternative iteration approach that yields mail objects directly. This method handles the enumeration details while providing access to individual message operations.

# Processing with each_mail iterator
Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
  processed_count = 0
  
  pop.each_mail do |mail|
    # Header analysis
    header_data = mail.header
    content_type = header_data[/^Content-Type: (.*)$/i, 1]
    
    # Size-based processing decisions
    if mail.length < 10_000  # Small messages only
      message_body = mail.pop
      
      # Simple text extraction
      if content_type&.include?('text/plain')
        puts "Text message: #{message_body[0..200]}"
      end
      
      mail.delete
      processed_count += 1
    else
      puts "Skipping large message (#{mail.length} bytes)"
    end
  end
  
  puts "Processed #{processed_count} messages"
end

Advanced Usage

Secure connections through POP3S require SSL/TLS configuration. Net::POP3 supports encrypted connections using the enable_ssl method or by specifying SSL parameters during connection establishment.

require 'net/pop'
require 'openssl'

# POP3S with SSL verification
Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_PEER)
Net::POP3.start('mail.server.com', 995, 'username', 'password') do |pop|
  puts "Secure connection established"
  puts "SSL cipher: #{pop.instance_variable_get(:@socket).cipher[0]}"
  
  pop.mails.each do |mail|
    # Process securely retrieved messages
    content = mail.pop
    puts "Secure message retrieval: #{content.length} bytes"
  end
end

Connection timeout and retry logic handles network reliability issues. Custom timeout values and retry strategies can improve robustness in unstable network conditions.

# Custom timeout and retry configuration
def robust_pop_retrieval(server, port, username, password, retries = 3)
  attempt = 0
  
  begin
    attempt += 1
    
    # Custom timeout settings
    pop = Net::POP3.new(server, port)
    pop.read_timeout = 30    # Read timeout
    pop.open_timeout = 10    # Connection timeout
    
    pop.start(username, password) do |session|
      yield session
    end
    
  rescue Net::TimeoutError, Errno::ECONNRESET => e
    if attempt < retries
      puts "Connection failed (attempt #{attempt}), retrying..."
      sleep(2 ** attempt)  # Exponential backoff
      retry
    else
      raise "Failed after #{retries} attempts: #{e.message}"
    end
  end
end

# Usage with retry logic
robust_pop_retrieval('mail.server.com', 110, 'user', 'pass') do |pop|
  pop.mails.each { |mail| mail.delete if mail.header.include?('spam') }
end

APOP authentication provides enhanced security by avoiding plain-text password transmission. This method uses MD5 hashing of the password combined with a server timestamp.

# APOP authentication
Net::POP3.APOP('mail.server.com', 110, 'username', 'password') do |pop|
  puts "APOP authentication successful"
  
  # Secure message processing
  pop.mails.select { |mail| mail.length < 5000 }.each do |mail|
    content = mail.pop
    # Process smaller messages with APOP security
    
    # Conditional deletion based on content analysis
    if content.include?('unsubscribe')
      mail.delete
      puts "Deleted newsletter message"
    end
  end
end

Partial message retrieval optimizes bandwidth usage for large messages. The top method retrieves message headers plus a specified number of body lines without downloading complete messages.

Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
  pop.mails.each do |mail|
    # Get headers plus first 10 lines of body
    partial_content = mail.top(10)
    
    lines = partial_content.split("\n")
    header_end = lines.index("") || 0
    preview_lines = lines[(header_end + 1)..(header_end + 10)]
    
    puts "Message preview:"
    puts preview_lines.join("\n")
    
    # Decision to retrieve full message
    if preview_lines.any? { |line| line.include?('attachment') }
      puts "Message has attachments, retrieving full content"
      full_content = mail.pop
      
      # Process attachments
      process_attachments(full_content)
      mail.delete
    else
      puts "Text-only message, skipping full retrieval"
    end
  end
end

def process_attachments(message_content)
  # Attachment processing logic
  boundary_match = message_content[/boundary="([^"]+)"/]
  return unless boundary_match
  
  boundary = boundary_match[1]
  parts = message_content.split("--#{boundary}")
  
  parts.each_with_index do |part, index|
    next if index == 0  # Skip preamble
    
    if part.include?('Content-Disposition: attachment')
      filename = part[/filename="([^"]+)"/, 1]
      puts "Found attachment: #{filename}" if filename
    end
  end
end

Bulk operations and message filtering implement efficient processing patterns for high-volume email handling. These patterns minimize server round-trips while maintaining processing flexibility.

# Efficient bulk processing with filtering
Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
  # Pre-filter by size to avoid large downloads
  small_messages = pop.mails.select { |mail| mail.length < 50_000 }
  large_messages = pop.mails.select { |mail| mail.length >= 50_000 }
  
  puts "Processing #{small_messages.length} small messages"
  puts "Deferring #{large_messages.length} large messages"
  
  # Batch header analysis
  headers_batch = small_messages.map do |mail|
    {
      mail: mail,
      headers: mail.header,
      number: mail.number
    }
  end
  
  # Filter by header content
  urgent_messages = headers_batch.select do |msg|
    msg[:headers].match?(/Priority: (high|urgent)/i) ||
    msg[:headers].match?(/X-Priority: [12]/i)
  end
  
  # Process urgent messages first
  urgent_messages.each do |msg|
    content = msg[:mail].pop
    puts "Processing urgent message #{msg[:number]}"
    
    # Custom urgent processing logic
    process_urgent_email(content)
    msg[:mail].delete
  end
  
  # Handle remaining small messages
  (small_messages - urgent_messages.map { |m| m[:mail] }).each do |mail|
    if mail.header.include?('newsletter')
      mail.delete  # Delete without retrieval
    else
      content = mail.pop
      process_regular_email(content)
      mail.delete
    end
  end
end

def process_urgent_email(content)
  # Urgent message processing
  puts "URGENT: #{content[/Subject: (.*)/, 1]}"
end

def process_regular_email(content)
  # Regular message processing
  puts "Regular: #{content[/Subject: (.*)/, 1]}"
end

Error Handling & Debugging

POP3 operations generate various exception types corresponding to protocol errors, network issues, and authentication failures. Understanding these exceptions enables robust error handling and appropriate recovery strategies.

require 'net/pop'

def handle_pop_errors(server, port, username, password)
  begin
    Net::POP3.start(server, port, username, password) do |pop|
      pop.mails.each do |mail|
        content = mail.pop
        process_message(content)
        mail.delete
      end
    end
    
  rescue Net::POPAuthenticationError => e
    puts "Authentication failed: #{e.message}"
    puts "Check username and password"
    # Log authentication failure for security monitoring
    log_auth_failure(username, server)
    
  rescue Net::POPServerBusy => e
    puts "Server busy: #{e.message}"
    puts "Retry after delay"
    return :retry_later
    
  rescue Net::POPError => e
    puts "POP protocol error: #{e.message}"
    puts "Response: #{e.response}" if e.respond_to?(:response)
    # Handle protocol-specific errors
    
  rescue Net::TimeoutError => e
    puts "Connection timeout: #{e.message}"
    puts "Check network connectivity and server status"
    return :network_error
    
  rescue Errno::ECONNREFUSED => e
    puts "Connection refused: #{e.message}"
    puts "Server may be down or port blocked"
    return :connection_refused
    
  rescue Errno::EHOSTUNREACH => e
    puts "Host unreachable: #{e.message}"
    puts "Check DNS resolution and routing"
    return :host_unreachable
    
  rescue StandardError => e
    puts "Unexpected error: #{e.class} - #{e.message}"
    puts e.backtrace.first(5).join("\n")
    return :unknown_error
  end
  
  :success
end

def log_auth_failure(username, server)
  timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
  puts "AUTH_FAIL: #{timestamp} - User: #{username}, Server: #{server}"
end

def process_message(content)
  # Message processing with error handling
  puts "Processing: #{content[/Subject: (.*)/, 1] || 'No subject'}"
end

Connection state debugging requires understanding POP3 session phases and server responses. The protocol's stateful nature means connection interruptions can leave messages in unexpected states.

class DebugPOPSession
  def initialize(server, port, username, password)
    @server = server
    @port = port
    @username = username
    @password = password
    @debug_mode = true
  end
  
  def retrieve_with_debugging
    pop = Net::POP3.new(@server, @port)
    
    # Enable protocol debugging
    pop.set_debug_output($stderr) if @debug_mode
    
    begin
      puts "Connecting to #{@server}:#{@port}"
      pop.start(@username, @password)
      puts "Connection established, entering transaction state"
      
      # Check server capabilities
      puts "Server greeting: #{pop.greeting}" if pop.respond_to?(:greeting)
      
      # Message enumeration
      message_count = pop.n_mails
      total_size = pop.n_bytes
      puts "Messages available: #{message_count} (#{total_size} bytes)"
      
      pop.mails.each_with_index do |mail, index|
        puts "\n--- Message #{index + 1} ---"
        puts "Number: #{mail.number}"
        puts "Size: #{mail.length} bytes"
        
        begin
          # Test header retrieval
          header = mail.header
          puts "Headers retrieved: #{header.split("\n").length} lines"
          
          # Test partial retrieval
          preview = mail.top(5)
          puts "Preview retrieved: #{preview.length} characters"
          
          # Full retrieval only for small messages
          if mail.length < 1000
            content = mail.pop
            puts "Full content retrieved: #{content.length} characters"
            
            # Test deletion
            mail.delete
            puts "Message marked for deletion"
          else
            puts "Skipping large message retrieval"
          end
          
        rescue Net::POPError => e
          puts "Error processing message #{mail.number}: #{e.message}"
          next
        end
      end
      
      puts "\nEntering update state (committing deletions)"
      
    rescue Net::POPError => e
      puts "POP error during session: #{e.class} - #{e.message}"
      puts "Session state may be inconsistent"
      
    ensure
      if pop.started?
        puts "Closing connection and committing changes"
        pop.finish
      else
        puts "Connection was not established or already closed"
      end
    end
  end
end

# Usage with debugging
debug_session = DebugPOPSession.new('mail.server.com', 110, 'user', 'pass')
debug_session.retrieve_with_debugging

Message parsing errors occur when handling malformed or corrupted messages. Implementing defensive parsing prevents individual message errors from disrupting entire sessions.

def safe_message_processing(pop_session)
  successful_processed = 0
  failed_messages = []
  
  pop_session.mails.each do |mail|
    begin
      # Validate message before processing
      if mail.length == 0
        puts "Warning: Zero-length message #{mail.number}"
        next
      end
      
      # Safe header parsing
      headers = parse_headers_safely(mail.header)
      
      # Content validation before full retrieval
      if headers[:content_length] && headers[:content_length] > 10_000_000
        puts "Skipping oversized message #{mail.number}"
        next
      end
      
      # Retrieve with encoding handling
      content = mail.pop
      
      # Validate encoding
      unless content.valid_encoding?
        puts "Invalid encoding in message #{mail.number}, attempting repair"
        content = content.encode('UTF-8', invalid: :replace, undef: :replace)
      end
      
      # Process successfully validated message
      process_validated_message(content, headers)
      mail.delete
      successful_processed += 1
      
    rescue StandardError => e
      error_info = {
        number: mail.number,
        error: e.class.name,
        message: e.message,
        size: mail.length
      }
      
      failed_messages << error_info
      puts "Failed to process message #{mail.number}: #{e.message}"
      
      # Continue processing other messages
      next
    end
  end
  
  puts "\nProcessing summary:"
  puts "Successfully processed: #{successful_processed}"
  puts "Failed messages: #{failed_messages.length}"
  
  failed_messages.each do |failure|
    puts "  Message #{failure[:number]}: #{failure[:error]} - #{failure[:message]}"
  end
end

def parse_headers_safely(header_text)
  headers = {}
  
  begin
    header_text.split("\n").each do |line|
      next if line.strip.empty?
      
      if line.match(/^([^:]+):\s*(.*)$/)
        key = $1.downcase.gsub('-', '_').to_sym
        value = $2.strip
        headers[key] = value
      end
    end
    
    # Parse specific headers with validation
    headers[:content_length] = headers[:content_length].to_i if headers[:content_length]
    
  rescue StandardError => e
    puts "Header parsing error: #{e.message}"
    headers[:parse_error] = e.message
  end
  
  headers
end

def process_validated_message(content, headers)
  subject = headers[:subject] || "No subject"
  from = headers[:from] || "Unknown sender"
  
  puts "Processing: #{subject} from #{from}"
  puts "Content length: #{content.length} characters"
end

Production Patterns

Production email processing requires robust connection management, efficient resource usage, and comprehensive error handling. These patterns address scalability, reliability, and monitoring requirements in production environments.

require 'net/pop'
require 'logger'
require 'timeout'

class ProductionEmailProcessor
  def initialize(config)
    @config = config
    @logger = Logger.new(@config[:log_file] || STDOUT)
    @logger.level = Logger::INFO
    @stats = {
      processed: 0,
      deleted: 0,
      errors: 0,
      bytes_processed: 0,
      start_time: Time.now
    }
  end
  
  def process_mailbox
    @logger.info "Starting email processing for #{@config[:username]}@#{@config[:server]}"
    
    retry_count = 0
    max_retries = @config[:max_retries] || 3
    
    begin
      # Connection with comprehensive timeout handling
      Timeout::timeout(@config[:session_timeout] || 300) do
        establish_connection do |pop|
          process_messages(pop)
        end
      end
      
      log_completion_stats
      
    rescue Net::POPAuthenticationError => e
      @logger.error "Authentication failed: #{e.message}"
      raise # Don't retry auth failures
      
    rescue Net::TimeoutError, Errno::ECONNRESET, Errno::ECONNREFUSED => e
      retry_count += 1
      @logger.warn "Connection error (attempt #{retry_count}): #{e.message}"
      
      if retry_count < max_retries
        delay = [2 ** retry_count, 60].min  # Exponential backoff, max 60s
        @logger.info "Retrying in #{delay} seconds..."
        sleep(delay)
        retry
      else
        @logger.error "Max retries exceeded, failing"
        raise
      end
      
    rescue StandardError => e
      @logger.error "Unexpected error: #{e.class} - #{e.message}"
      @logger.error e.backtrace.first(10).join("\n")
      raise
    end
  end
  
  private
  
  def establish_connection
    if @config[:ssl]
      Net::POP3.enable_ssl
      port = @config[:port] || 995
    else
      port = @config[:port] || 110
    end
    
    pop = Net::POP3.new(@config[:server], port)
    pop.read_timeout = @config[:read_timeout] || 30
    pop.open_timeout = @config[:open_timeout] || 10
    
    pop.start(@config[:username], @config[:password]) do |session|
      @logger.info "Connected successfully, #{session.n_mails} messages available"
      yield session
    end
  end
  
  def process_messages(pop)
    # Sort messages by size for efficient processing
    messages = pop.mails.sort_by(&:length)
    
    # Process in batches to manage memory
    batch_size = @config[:batch_size] || 50
    
    messages.each_slice(batch_size) do |batch|
      process_message_batch(batch)
      
      # Memory cleanup between batches
      GC.start if @config[:gc_between_batches]
    end
  end
  
  def process_message_batch(messages)
    @logger.info "Processing batch of #{messages.length} messages"
    
    messages.each do |mail|
      begin
        process_single_message(mail)
      rescue StandardError => e
        @stats[:errors] += 1
        @logger.error "Error processing message #{mail.number}: #{e.message}"
        # Continue with next message
      end
    end
  end
  
  def process_single_message(mail)
    # Size-based processing decisions
    if mail.length > (@config[:max_message_size] || 10_000_000)
      @logger.warn "Skipping oversized message #{mail.number} (#{mail.length} bytes)"
      return
    end
    
    # Header-only filtering for efficiency
    headers = mail.header
    
    if should_skip_message?(headers)
      @logger.debug "Skipping filtered message #{mail.number}"
      mail.delete if @config[:delete_filtered]
      return
    end
    
    # Retrieve and process
    content = mail.pop
    @stats[:bytes_processed] += content.length
    
    # Custom message processing
    result = process_message_content(content, headers)
    
    if result[:delete]
      mail.delete
      @stats[:deleted] += 1
      @logger.debug "Deleted message #{mail.number}"
    end
    
    @stats[:processed] += 1
    
    # Progress logging
    if @stats[:processed] % 100 == 0
      @logger.info "Processed #{@stats[:processed]} messages"
    end
  end
  
  def should_skip_message?(headers)
    # Example filtering logic
    spam_indicators = ['X-Spam-Flag: YES', 'X-Spam-Status: Yes']
    headers.match?(Regexp.union(spam_indicators))
  end
  
  def process_message_content(content, headers)
    # Implement custom business logic
    subject = headers[/^Subject: (.*)$/m, 1] || 'No subject'
    
    # Example: Archive important messages
    if subject.match?(/\b(urgent|important|critical)\b/i)
      archive_message(content, subject)
      return { delete: false }  # Keep important messages
    end
    
    # Example: Process and delete routine messages
    extract_data_from_message(content)
    return { delete: true }
  end
  
  def archive_message(content, subject)
    # Implementation for archiving important messages
    @logger.info "Archiving important message: #{subject}"
  end
  
  def extract_data_from_message(content)
    # Implementation for data extraction
    @logger.debug "Extracting data from message"
  end
  
  def log_completion_stats
    duration = Time.now - @stats[:start_time]
    
    @logger.info "Processing completed in #{duration.round(2)} seconds"
    @logger.info "Messages processed: #{@stats[:processed]}"
    @logger.info "Messages deleted: #{@stats[:deleted]}"
    @logger.info "Processing errors: #{@stats[:errors]}"
    @logger.info "Bytes processed: #{@stats[:bytes_processed]}"
    @logger.info "Average rate: #{(@stats[:processed] / duration).round(2)} messages/second"
  end
end

# Production configuration and usage
config = {
  server: 'mail.company.com',
  port: 995,
  ssl: true,
  username: 'processor@company.com',
  password: ENV['EMAIL_PASSWORD'],
  max_retries: 5,
  session_timeout: 600,
  read_timeout: 45,
  batch_size: 100,
  max_message_size: 50_000_000,
  delete_filtered: true,
  gc_between_batches: true,
  log_file: '/var/log/email_processor.log'
}

processor = ProductionEmailProcessor.new(config)
processor.process_mailbox

Monitoring and alerting integration provides visibility into email processing operations and enables proactive issue detection.

require 'net/pop'
require 'json'
require 'net/http'

class MonitoredEmailProcessor
  def initialize(config, metrics_config = {})
    @config = config
    @metrics_config = metrics_config
    @metrics = {
      connection_attempts: 0,
      connection_failures: 0,
      messages_processed: 0,
      processing_errors: 0,
      session_duration: 0,
      last_successful_run: nil
    }
  end
  
  def process_with_monitoring
    start_time = Time.now
    
    begin
      @metrics[:connection_attempts] += 1
      
      Net::POP3.start(@config[:server], @config[:port], 
                      @config[:username], @config[:password]) do |pop|
        
        @metrics[:session_duration] = Time.now - start_time
        message_count = pop.n_mails
        
        # Alert on unusual message volume
        check_message_volume_alert(message_count)
        
        pop.mails.each do |mail|
          begin
            process_message_with_metrics(mail)
            @metrics[:messages_processed] += 1
            
          rescue StandardError => e
            @metrics[:processing_errors] += 1
            send_error_alert(e, mail.number)
          end
        end
        
        @metrics[:last_successful_run] = Time.now
      end
      
      send_success_metrics
      
    rescue Net::POPError, Net::TimeoutError => e
      @metrics[:connection_failures] += 1
      send_connection_alert(e)
      raise
      
    ensure
      @metrics[:session_duration] = Time.now - start_time
    end
  end
  
  private
  
  def process_message_with_metrics(mail)
    message_start = Time.now
    
    # Message processing logic
    content = mail.pop
    
    # Track message processing time
    processing_time = Time.now - message_start
    
    # Alert on slow processing
    if processing_time > 30  # 30 seconds threshold
      send_performance_alert(mail.number, processing_time)
    end
    
    # Example processing
    if content.include?('order confirmation')
      process_order_confirmation(content)
    elsif content.include?('error report')
      process_error_report(content)
    end
    
    mail.delete
  end
  
  def check_message_volume_alert(count)
    expected_range = @config[:expected_message_range] || (0..1000)
    
    unless expected_range.include?(count)
      alert_data = {
        alert_type: 'unusual_volume',
        message_count: count,
        expected_range: expected_range,
        timestamp: Time.now.iso8601
      }
      
      send_alert(alert_data)
    end
  end
  
  def send_error_alert(error, message_number)
    alert_data = {
      alert_type: 'processing_error',
      error_class: error.class.name,
      error_message: error.message,
      message_number: message_number,
      timestamp: Time.now.iso8601,
      error_count: @metrics[:processing_errors]
    }
    
    send_alert(alert_data)
  end
  
  def send_connection_alert(error)
    alert_data = {
      alert_type: 'connection_failure',
      error_class: error.class.name,
      error_message: error.message,
      server: @config[:server],
      failure_count: @metrics[:connection_failures],
      timestamp: Time.now.iso8601
    }
    
    send_alert(alert_data)
  end
  
  def send_performance_alert(message_number, processing_time)
    alert_data = {
      alert_type: 'slow_processing',
      message_number: message_number,
      processing_time: processing_time,
      threshold: 30,
      timestamp: Time.now.iso8601
    }
    
    send_alert(alert_data)
  end
  
  def send_success_metrics
    return unless @metrics_config[:webhook_url]
    
    metrics_data = {
      type: 'success_metrics',
      server: @config[:server],
      metrics: @metrics,
      timestamp: Time.now.iso8601
    }
    
    send_webhook(metrics_data)
  end
  
  def send_alert(alert_data)
    puts "ALERT: #{alert_data[:alert_type]} - #{alert_data}"
    
    # Send to monitoring system
    send_webhook(alert_data) if @metrics_config[:webhook_url]
    
    # Log to file
    if @metrics_config[:alert_log_file]
      File.open(@metrics_config[:alert_log_file], 'a') do |f|
        f.puts "#{Time.now.iso8601}: #{alert_data.to_json}"
      end
    end
  end
  
  def send_webhook(data)
    uri = URI(@metrics_config[:webhook_url])
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    
    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = 'application/json'
    request.body = data.to_json
    
    response = http.request(request)
    puts "Webhook response: #{response.code}" unless response.code == '200'
    
  rescue StandardError => e
    puts "Failed to send webhook: #{e.message}"
  end
  
  def process_order_confirmation(content)
    # Business logic for order confirmations
    puts "Processing order confirmation"
  end
  
  def process_error_report(content)
    # Business logic for error reports
    puts "Processing error report - escalating"
  end
end

Common Pitfalls

Message deletion timing represents a significant pitfall in POP3 operations. Messages marked for deletion during a session remain on the server until the connection closes successfully. Connection interruptions or exceptions can prevent deletions from being committed.

require 'net/pop'

# PROBLEMATIC: Deletion state inconsistency
def unreliable_deletion
  Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
    pop.mails.each do |mail|
      content = mail.pop
      
      if content.include?('delete me')
        mail.delete  # Marked for deletion
        puts "Message #{mail.number} marked for deletion"
        
        # DANGER: If an exception occurs here, deletion is lost
        risky_processing(content)  # May raise exception
      end
    end
    # Deletions committed only if block completes successfully
  end
rescue StandardError => e
  # Connection closed abnormally - deletions are NOT committed
  puts "Error occurred: #{e.message}"
  puts "Marked deletions were NOT committed to server"
end

def risky_processing(content)
  # Simulated processing that might fail
  raise "Processing failed" if content.include?('bad data')
end

# BETTER: Explicit deletion control
def reliable_deletion
  pop = Net::POP3.new('mail.server.com', 110)
  deletion_queue = []
  
  begin
    pop.start('user', 'pass')
    
    pop.mails.each do |mail|
      content = mail.pop
      
      # Process first, mark for deletion after success
      begin
        process_message(content)
        
        # Only add to deletion queue after successful processing
        if content.include?('delete me')
          deletion_queue << mail
          puts "Message #{mail.number} queued for deletion"
        end
        
      rescue StandardError => e
        puts "Processing failed for message #{mail.number}: #{e.message}"
        # Don't add to deletion queue on processing failure
      end
    end
    
    # Batch deletions after all processing
    deletion_queue.each do |mail|
      mail.delete
      puts "Deleting message #{mail.number}"
    end
    
    puts "Committing #{deletion_queue.length} deletions"
    
  ensure
    pop.finish if pop.started?  # Commits deletions
  end
end

def process_message(content)
  # Safe message processing
  puts "Processing: #{content[0..100]}"
end

Character encoding issues frequently occur with international email content. POP3 messages may contain various encodings, and Ruby's string handling requires explicit encoding management.

# PROBLEMATIC: Encoding assumptions
def naive_message_processing
  Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
    pop.mails.each do |mail|
      content = mail.pop
      
      # DANGER: Assumes UTF-8 encoding
      if content.include?('résumé')  # May fail with encoding errors
        puts "Found résumé in message"
      end
      
      # DANGER: String operations on mixed encodings
      subject = content[/^Subject: (.*)$/m, 1]
      puts "Subject: #{subject.upcase}"  # May raise encoding error
    end
  end
rescue Encoding::CompatibilityError => e
  puts "Encoding error: #{e.message}"
end

# BETTER: Proper encoding handling
def encoding_aware_processing
  Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
    pop.mails.each do |mail|
      content = mail.pop
      
      # Detect and handle encoding
      unless content.valid_encoding?
        puts "Invalid encoding detected, attempting repair"
        
        # Try common encodings
        ['UTF-8', 'ISO-8859-1', 'Windows-1252'].each do |encoding|
          begin
            repaired = content.force_encoding(encoding)
            if repaired.valid_encoding?
              content = repaired.encode('UTF-8')
              puts "Repaired encoding using #{encoding}"
              break
            end
          rescue Encoding::UndefinedConversionError
            next
          end
        end
        
        # Fallback: Replace invalid characters
        unless content.valid_encoding?
          content = content.encode('UTF-8', invalid: :replace, undef: :replace)
          puts "Used replacement characters for invalid bytes"
        end
      end
      
      # Safe string operations
      begin
        if content.include?('résumé')
          puts "Found résumé in message"
        end
        
        subject = extract_subject_safely(content)
        puts "Subject: #{subject}" if subject
        
      rescue Encoding::CompatibilityError => e
        puts "Encoding compatibility issue: #{e.message}"
        puts "Content encoding: #{content.encoding}"
      end
    end
  end
end

def extract_subject_safely(content)
  # Extract subject with encoding awareness
  match = content.match(/^Subject: (.*)$/m)
  return nil unless match
  
  subject = match[1].strip
  
  # Handle encoded-word format (RFC 2047)
  if subject.match(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/)
    encoding = $1
    transfer_encoding = $2
    encoded_text = $3
    
    begin
      if transfer_encoding.upcase == 'B'
        decoded = encoded_text.unpack1('m')  # Base64
      elsif transfer_encoding.upcase == 'Q'
        decoded = encoded_text.tr('_', ' ').unpack1('M')  # Quoted-printable
      end
      
      subject = decoded.force_encoding(encoding).encode('UTF-8')
    rescue StandardError => e
      puts "Failed to decode subject: #{e.message}"
      # Return original subject
    end
  end
  
  subject
end

Connection timeout handling requires understanding both Ruby's timeout mechanisms and POP3 protocol behavior. Improper timeout handling can leave connections in undefined states.

require 'timeout'

# PROBLEMATIC: Improper timeout handling
def problematic_timeouts
  # DANGER: Global timeout can interrupt protocol mid-command
  Timeout::timeout(30) do
    Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
      pop.mails.each do |mail|
        # Timeout may occur during message retrieval
        content = mail.pop  # May be interrupted
        mail.delete         # Server state becomes inconsistent
      end
    end
  end
rescue Timeout::Error
  puts "Operation timed out - connection state unknown"
end

# BETTER: Granular timeout management
def proper_timeout_handling
  pop = Net::POP3.new('mail.server.com', 110)
  
  # Set appropriate timeouts for different operations
  pop.open_timeout = 10   # Connection establishment
  pop.read_timeout = 60   # Individual read operations
  
  begin
    pop.start('user', 'pass')
    
    # Check server responsiveness
    message_count = pop.n_mails
    puts "Retrieved message count: #{message_count}"
    
    pop.mails.each_with_index do |mail, index|
      operation_start = Time.now
      
      begin
        # Monitor individual message processing time
        Timeout::timeout(120) do  # Per-message timeout
          content = mail.pop
          
          processing_time = Time.now - operation_start
          if processing_time > 30
            puts "Slow message retrieval: #{processing_time}s for message #{index + 1}"
          end
          
          # Process with its own timeout
          process_with_timeout(content, 30)
          
          # Safe deletion after successful processing
          mail.delete
        end
        
      rescue Timeout::Error
        puts "Timeout processing message #{index + 1}, skipping"
        # Don't delete message on timeout
        next
        
      rescue Net::ReadTimeout
        puts "Network read timeout for message #{index + 1}"
        # Connection may still be valid, continue with next message
        next
      end
    end
    
  rescue Net::OpenTimeout
    puts "Failed to establish connection within timeout"
    raise
    
  rescue Net::ReadTimeout
    puts "Network read timeout during session"
    # Connection is likely broken
    raise
    
  ensure
    # Ensure clean connection closure
    if pop.started?
      begin
        Timeout::timeout(10) do
          pop.finish
        end
      rescue Timeout::Error
        puts "Timeout during connection closure"
        # Force close if normal closure times out
      end
    end
  end
end

def process_with_timeout(content, timeout_seconds)
  Timeout::timeout(timeout_seconds) do
    # Message processing logic
    lines = content.split("\n")
    puts "Processing #{lines.length} lines"
    
    # Simulate processing work
    sleep(0.1) if lines.length > 1000  # Simulate heavy processing
  end
rescue Timeout::Error
  puts "Message processing timed out"
  raise
end

Memory management becomes critical when processing large volumes of messages or individual large messages. Ruby's garbage collection behavior and string handling patterns affect memory usage patterns.

# PROBLEMATIC: Memory accumulation
def memory_inefficient_processing
  all_messages = []
  
  Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
    pop.mails.each do |mail|
      # DANGER: Accumulating all message content in memory
      content = mail.pop
      all_messages << {
        number: mail.number,
        content: content,  # Large strings retained
        headers: parse_headers(content),
        processed: false
      }
    end
    
    # DANGER: All messages in memory simultaneously
    all_messages.each do |msg_data|
      process_large_message(msg_data[:content])
      msg_data[:processed] = true
      # Original content string still in memory
    end
  end
  
  # Memory pressure continues until method exit
  puts "Processed #{all_messages.length} messages"
end

# BETTER: Streaming and memory-conscious processing
def memory_efficient_processing
  processed_count = 0
  
  Net::POP3.start('mail.server.com', 110, 'user', 'pass') do |pop|
    # Process messages in batches to control memory usage
    batch_size = 10
    
    pop.mails.each_slice(batch_size) do |batch|
      batch.each do |mail|
        # Process one message at a time
        process_single_message_efficiently(mail)
        processed_count += 1
        
        # Explicit cleanup for large messages
        if mail.length > 1_000_000
          GC.start  # Force garbage collection
        end
      end
      
      # Progress reporting without retaining data
      puts "Processed #{processed_count} messages" if processed_count % 50 == 0
      
      # Batch-level memory cleanup
      GC.start
    end
  end
end

def process_single_message_efficiently(mail)
  # Check size before retrieval
  if mail.length > 50_000_000  # 50MB threshold
    puts "Skipping oversized message #{mail.number} (#{mail.length} bytes)"
    return
  end
  
  # Stream headers for filtering without full retrieval
  headers = mail.header
  
  # Filter before expensive full retrieval
  return if headers.include?('X-Spam-Flag: YES')
  
  # Retrieve content only when needed
  content = mail.pop
  
  begin
    # Process in chunks for large messages
    if content.length > 1_000_000
      process_large_content_in_chunks(content)
    else
      process_normal_content(content)
    end
    
    mail.delete
    
  ensure
    # Explicit cleanup for large strings
    content = nil  # Allow immediate GC
  end
end

def process_large_content_in_chunks(content)
  chunk_size = 100_000  # 100KB chunks
  offset = 0
  
  while offset < content.length
    chunk_end = [offset + chunk_size, content.length].min
    chunk = content[offset...chunk_end]
    
    # Process chunk
    process_content_chunk(chunk, offset)
    
    # Clear chunk reference
    chunk = nil
    offset = chunk_end
  end
end

def process_content_chunk(chunk, offset)
  puts "Processing chunk at offset #{offset}: #{chunk.length} bytes"
  # Chunk processing logic
end

def process_normal_content(content)
  puts "Processing normal message: #{content.length} bytes"
  # Regular processing logic
end

def parse_headers(content)
  # Simple header extraction
  header_section = content[/\A(.*?)\n\n/m, 1]
  return {} unless header_section
  
  headers = {}
  header_section.split("\n").each do |line|
    if line.match(/^([^:]+):\s*(.*)$/)
      headers[$1.downcase] = $2
    end
  end
  headers
end

def process_large_message(content)
  # Simulated processing
  puts "Processing large message: #{content.length} characters"
end

Reference

Core Classes and Methods

Class/Method Parameters Returns Description
Net::POP3.new(address, port) address (String), port (Integer) Net::POP3 Creates POP3 connection object
Net::POP3.start(address, port, account, password, &block) Connection params, credentials, block Object or nil Establishes session with automatic cleanup
Net::POP3.APOP(address, port, account, password, &block) Connection params, credentials, block Object or nil APOP authentication session
Net::POP3.enable_ssl(*args) SSL verification options nil Enables SSL for subsequent connections
#start(account, password) account (String), password (String) self Authenticates and enters transaction state
#finish None nil Closes connection and commits deletions
#started? None Boolean Checks if session is active
#n_mails None Integer Returns number of messages
#n_bytes None Integer Returns total mailbox size in bytes
#mails None Array<Net::POPMail> Returns array of mail objects
#each_mail(&block) Block nil Iterates over mail objects
#delete_all None nil Marks all messages for deletion
#reset None nil Unmarks messages marked for deletion

POPMail Object Methods

Method Parameters Returns Description
#number None Integer Message sequence number
#length None Integer Message size in bytes
#header None String Retrieves message headers
#top(lines) lines (Integer) String Headers plus specified body lines
#pop None String Retrieves complete message
#delete None nil Marks message for deletion
#deleted? None Boolean Checks deletion status
#unique_id None String Server-assigned unique identifier

Configuration Attributes

Attribute Type Default Description
open_timeout Integer 30 Connection timeout in seconds
read_timeout Integer 60 Read operation timeout in seconds
port Integer 110 Server port (995 for SSL)
use_ssl Boolean false Enable SSL/TLS encryption

Exception Hierarchy

Exception Inherits From Description
Net::POPError Net::ProtocolError Base class for POP3 errors
Net::POPAuthenticationError Net::POPError Authentication failures
Net::POPServerBusy Net::POPError Server temporarily unavailable
Net::POPBadResponse Net::POPError Invalid server response
Net::TimeoutError Timeout::Error Operation timeout

SSL Configuration Options

Option Type Description
OpenSSL::SSL::VERIFY_NONE Integer Disable certificate verification
OpenSSL::SSL::VERIFY_PEER Integer Verify server certificate
ca_file String Path to CA certificate file
ca_path String Path to CA certificate directory
cert OpenSSL::X509::Certificate Client certificate
key OpenSSL::PKey Client private key

Common Response Codes

Code Meaning Context
+OK Success Command completed successfully
-ERR Error Command failed or invalid
+OK message follows Data transfer Message content follows
-ERR no such message Invalid message Message number doesn't exist
-ERR login failure Authentication Invalid credentials

Protocol States

State Description Available Commands
Authorization Initial state after connection USER, PASS, APOP, QUIT
Transaction After successful authentication STAT, LIST, RETR, DELE, RSET, QUIT
Update During connection closure Automatic deletion processing

Performance Considerations

Operation Typical Speed Memory Usage Network I/O
Connection establishment 1-3 seconds Low 2-3 round trips
Authentication < 1 second Low 1-2 round trips
Message enumeration < 1 second Low 1 round trip
Header retrieval < 1 second per message Medium 1 round trip per message
Full message retrieval Variable by size High 1 round trip per message
Message deletion < 1 second Low 1 round trip per message

Security Considerations

Aspect Standard POP3 POP3S Recommendations
Authentication Plain text Encrypted Use POP3S or APOP
Data transmission Plain text Encrypted Always use POP3S in production
Password storage Server responsibility Server responsibility Use application passwords when available
Connection security None TLS/SSL Verify certificates in production