CrackedRuby CrackedRuby

Overview

SMTP, POP3, and IMAP form the core protocol suite for email communication on the internet. SMTP (Simple Mail Transfer Protocol) handles message transmission between mail servers and from clients to servers. POP3 (Post Office Protocol version 3) downloads messages from a mail server to a local client, typically removing them from the server. IMAP (Internet Message Access Protocol) provides remote access to messages stored on a server, maintaining server-side state and folder hierarchies.

These protocols operate at the application layer of the TCP/IP stack. SMTP uses port 25 for server-to-server communication, port 587 for client submission (often with STARTTLS), and port 465 for implicit TLS. POP3 uses port 110 for plain connections and port 995 for TLS. IMAP uses port 143 for plain connections and port 993 for TLS.

The relationship between these protocols reflects different aspects of email workflow. A typical email journey begins with SMTP transmitting the message from sender to recipient's mail server. The recipient then uses either POP3 to download and store messages locally, or IMAP to access messages that remain on the server. This separation of concerns allows each protocol to specialize in its specific function.

# Basic protocol interaction flow
require 'net/smtp'
require 'net/imap'

# Sending via SMTP
Net::SMTP.start('smtp.example.com', 587) do |smtp|
  smtp.send_message(message, 'from@example.com', 'to@example.com')
end

# Receiving via IMAP
imap = Net::IMAP.new('imap.example.com', 993, true)
imap.login('user@example.com', 'password')
imap.select('INBOX')
# => Messages remain on server

The protocols differ fundamentally in their operational models. SMTP operates as a push protocol, actively delivering messages to destination servers. POP3 and IMAP operate as pull protocols, with clients initiating connections to retrieve messages. SMTP uses a command-response model with text-based commands like HELO, MAIL FROM, RCPT TO, and DATA. POP3 provides simple commands (USER, PASS, STAT, LIST, RETR, DELE) for session-based message retrieval. IMAP offers extensive capabilities including folder management, server-side search, partial message fetching, and persistent message flags.

Key Principles

SMTP establishes connections through a multi-step handshake process. The client initiates with HELO or EHLO, identifying itself to the server. The server responds with its capabilities, particularly SMTP extensions supported through ESMTP (Extended SMTP). The client then specifies the envelope sender with MAIL FROM, followed by one or more RCPT TO commands for recipients. The DATA command introduces the message content, terminated by a line containing only a period. The server accepts or rejects the message based on policy rules, recipient validity, and content analysis.

Message envelopes exist separately from message headers. The envelope, established through SMTP commands, controls routing and delivery. Headers within the message content provide metadata visible to recipients. This separation allows for forwarding, mailing lists, and blind carbon copies where envelope addresses differ from header addresses. Bounce messages return to the envelope sender, not the From header address.

# Envelope vs. headers demonstration
message = <<~MESSAGE
  From: display@example.com
  To: recipient@example.com
  Subject: Envelope Example
  
  Message body
MESSAGE

Net::SMTP.start('localhost', 25) do |smtp|
  # Envelope sender differs from From header
  smtp.send_message(
    message,
    'bounce-handler@example.com',  # Envelope sender
    ['actual-recipient@example.com']  # Envelope recipients
  )
end

POP3 operates through distinct session phases. The authorization phase authenticates the user through USER/PASS commands or APOP for digest authentication. The transaction phase allows message retrieval and deletion marking through RETR and DELE commands. STAT returns message count and total size. LIST provides per-message sizes. The update phase, triggered by QUIT, permanently removes messages marked for deletion. POP3 maintains exclusive locks on mailboxes during sessions, preventing concurrent access.

IMAP maintains persistent server-side state across sessions. Messages retain flags (Seen, Answered, Flagged, Deleted, Draft) that synchronize between clients. The protocol supports multiple simultaneous connections to the same mailbox, with servers broadcasting changes to all connected clients. Commands operate on sequence numbers or unique identifiers (UIDs). Sequence numbers change as messages are added or removed; UIDs remain constant for each message's lifetime.

# IMAP state management
imap = Net::IMAP.new('imap.example.com', 993, true)
imap.login('user', 'password')
imap.select('INBOX')

# Search returns sequence numbers
messages = imap.search(['UNSEEN'])
# => [1, 3, 5]

# UIDs provide stable references
uids = imap.uid_search(['UNSEEN'])
# => [1001, 1003, 1005]

# Fetch by UID instead of sequence number
imap.uid_fetch(1001, 'BODY[]')

IMAP folder hierarchies use mailbox paths separated by delimiters, typically period or slash characters. The NAMESPACE command reveals the server's folder structure conventions. Personal mailboxes, shared mailboxes, and public folders may exist in different namespaces with different access permissions. The LSUB command lists subscribed folders, while LIST shows all available folders.

Message formats follow RFC 5322 structure with headers and body separated by a blank line. Headers use field-name colon field-value format. Multiline headers indent continuation lines with whitespace. MIME (Multipurpose Internet Mail Extensions) encodes non-text content through Content-Type and Content-Transfer-Encoding headers. Multipart messages contain multiple MIME parts with boundary delimiters.

Ruby Implementation

Ruby's standard library provides Net::SMTP, Net::POP3, and Net::IMAP classes in the net-smtp, net-pop, and net-imap gems respectively. These libraries implement the protocol specifications with Ruby's object-oriented patterns and exception handling conventions.

Net::SMTP supports both simple send operations and session-based interactions. The class method start creates a connection, yields to a block for message transmission, then closes the connection. The instance maintains connection state and provides access to SMTP extensions through the capabilities method after EHLO.

require 'net/smtp'

# Simple send with automatic connection management
Net::SMTP.start(
  'smtp.example.com',
  587,
  'localhost',  # HELO domain
  'user',
  'password',
  :login  # Authentication method
) do |smtp|
  smtp.send_message(message_string, from, to_addresses)
end

# Manual session management for multiple messages
smtp = Net::SMTP.new('smtp.example.com', 587)
smtp.enable_starttls_auto
smtp.start('localhost', 'user', 'password', :login) do
  recipients.each do |recipient|
    message = build_message(recipient)
    smtp.send_message(message, from, recipient)
  end
end

The send_message method accepts message content as a string, envelope sender as a string, and recipients as a string or array. The method constructs proper SMTP commands internally. For more control, use the data method with a block that yields a socket-like object for writing message content line by line.

# Low-level message sending
smtp.start('localhost') do
  smtp.mailfrom('sender@example.com')
  smtp.rcptto('recipient1@example.com')
  smtp.rcptto('recipient2@example.com')
  
  smtp.data do |data_stream|
    data_stream.puts "From: sender@example.com"
    data_stream.puts "To: recipient1@example.com"
    data_stream.puts "Subject: Test"
    data_stream.puts ""
    data_stream.puts "Message body"
  end
end

Net::POP3 provides session management through the start class method or explicit connection handling. The class supports both plain authentication and APOP digest authentication. Sessions lock the mailbox, requiring proper cleanup through finish or using blocks that automatically close connections.

require 'net/pop'

# Retrieve all messages
Net::POP3.start('pop.example.com', 995, 'user', 'password') do |pop|
  if pop.mails.empty?
    puts "No messages"
  else
    pop.mails.each do |msg|
      puts "Message #{msg.number}:"
      puts msg.pop  # Retrieves and returns message content
      msg.delete  # Marks for deletion
    end
  end
end

# Check messages without retrieval
pop = Net::POP3.new('pop.example.com', 995)
pop.enable_ssl
pop.start('user', 'password')

message_count = pop.n_mails
total_size = pop.n_bytes

pop.each do |msg|
  puts "Message #{msg.number}: #{msg.length} bytes"
  header = msg.header  # Retrieves only headers
  puts header
end

pop.finish

The POPMail objects returned by mails or each represent individual messages. The pop method retrieves the full message content. The header method fetches only headers, reducing bandwidth. The top method retrieves headers plus a specified number of body lines. The delete method marks messages for deletion, with actual removal occurring when the session ends normally through finish or block exit.

Net::IMAP provides comprehensive protocol support through explicit method calls matching IMAP commands. The library exposes raw protocol operations while handling response parsing. Methods return structured data objects containing server responses.

require 'net/imap'

# Connect with implicit TLS
imap = Net::IMAP.new('imap.example.com', 993, true)

# Authenticate
imap.login('user@example.com', 'password')

# List mailboxes
mailboxes = imap.list('', '*')
mailboxes.each do |mb|
  puts "#{mb.name}: #{mb.attr.join(', ')}"
end

# Select mailbox
imap.select('INBOX')
# Returns mailbox data: message count, recent count, flags

# Search for messages
unseen_ids = imap.search(['UNSEEN'])
from_ids = imap.search(['FROM', 'boss@example.com'])
recent_ids = imap.search(['SINCE', '1-Jan-2024'])

# Fetch message data
messages = imap.fetch(unseen_ids, ['ENVELOPE', 'BODY[HEADER.FIELDS (FROM TO SUBJECT)]'])
messages.each do |msg_data|
  envelope = msg_data.attr['ENVELOPE']
  puts "From: #{envelope.from[0].mailbox}@#{envelope.from[0].host}"
  puts "Subject: #{envelope.subject}"
  
  headers = msg_data.attr['BODY[HEADER.FIELDS (FROM TO SUBJECT)]']
  puts headers
end

IMAP search criteria use array syntax where the first element specifies the search key and subsequent elements provide parameters. Complex searches combine multiple criteria. The NOT operator inverts criteria. The OR operator combines alternatives. Sequences of criteria are implicitly ANDed together.

# Complex search examples
flagged_and_unseen = imap.search(['UNFLAGGED', 'UNSEEN'])

large_recent = imap.search(['RECENT', 'LARGER', 100000])

from_or_to = imap.search(['OR', ['FROM', 'alice'], ['TO', 'alice']])

complex = imap.search([
  'SINCE', '1-Jan-2024',
  'NOT', ['SEEN'],
  'OR', ['FROM', 'important'], ['FLAGGED']
])

The fetch method retrieves message attributes specified by name. Common attributes include ENVELOPE (structured header data), BODY (message structure), BODYSTRUCTURE (detailed structure), BODY[section] (message content or parts), FLAGS (message flags), INTERNALDATE (server date), and RFC822.SIZE (message size). Partial fetches retrieve byte ranges for large messages or attachments.

# Fetch specific message parts
msg_data = imap.fetch(message_id, [
  'ENVELOPE',
  'BODY[HEADER]',
  'BODY[TEXT]',
  'BODY[1]',  # First MIME part
  'FLAGS',
  'INTERNALDATE'
]).first

# Partial fetch for large attachments
partial = imap.fetch(message_id, ['BODY[2]<0.8192>'])
# Fetches first 8192 bytes of second MIME part

Security Implications

Email protocols transmit authentication credentials and message content that require protection against interception and tampering. Unencrypted connections expose passwords to network sniffing and messages to inspection. SMTP originally provided no encryption, sending all data in plaintext. Modern implementations use STARTTLS to upgrade connections to TLS after initial handshake, or implicit TLS where encryption begins immediately.

STARTTLS operates through a two-phase connection. The client connects on the plain port and issues the STARTTLS command. The server confirms support, then both parties negotiate TLS encryption. All subsequent traffic flows through the encrypted channel. This approach maintains compatibility with legacy servers that lack encryption support, but creates an opportunity for downgrade attacks where an attacker strips the STARTTLS command to force plaintext communication.

# STARTTLS for SMTP
smtp = Net::SMTP.new('smtp.example.com', 587)
smtp.enable_starttls_auto  # Upgrades if server supports it
smtp.start('localhost', 'user', 'password', :login)

# Implicit TLS for SMTP
smtp = Net::SMTP.new('smtp.example.com', 465)
smtp.enable_tls  # Connects with immediate encryption
smtp.start('localhost', 'user', 'password', :login)

# Strict STARTTLS enforcement
smtp = Net::SMTP.new('smtp.example.com', 587)
smtp.enable_starttls  # Raises exception if STARTTLS unavailable
smtp.start('localhost', 'user', 'password', :login)

POP3 and IMAP follow similar encryption patterns. Port 110 (POP3) and port 143 (IMAP) traditionally used STARTTLS through STLS and STARTTLS commands respectively. Ports 995 (POP3S) and 993 (IMAPS) provide implicit TLS. Modern deployments disable plaintext ports entirely, accepting only encrypted connections.

Authentication mechanisms range from plaintext to challenge-response to token-based. PLAIN authentication sends username and password in Base64 encoding, providing no security without TLS. LOGIN authentication splits credentials across multiple exchanges but remains equivalent to PLAIN. CRAM-MD5 uses challenge-response with MD5 hashing, avoiding password transmission but vulnerable to rainbow tables. DIGEST-MD5 improves on CRAM-MD5 but has implementation inconsistencies. OAuth2 provides token-based authentication suitable for third-party applications accessing user accounts without handling passwords.

# Different authentication methods
# PLAIN (default, requires TLS)
smtp.start('localhost', 'user', 'password', :plain)

# LOGIN (equivalent to PLAIN)
smtp.start('localhost', 'user', 'password', :login)

# CRAM-MD5 (challenge-response)
smtp.start('localhost', 'user', 'password', :cram_md5)

# OAuth2 (requires external token acquisition)
imap = Net::IMAP.new('imap.gmail.com', 993, true)
imap.authenticate('XOAUTH2', 'user@gmail.com', access_token)

Certificate validation prevents man-in-the-middle attacks where an attacker intercepts connections by presenting fraudulent certificates. Ruby's Net libraries use OpenSSL for TLS, with certificate validation enabled by default. The validation checks certificate signatures, expiration dates, and hostname matching. Self-signed certificates or certificates with hostname mismatches cause connection failures unless explicitly allowed.

# Strict certificate validation (default)
imap = Net::IMAP.new('imap.example.com', 993, true)

# Allow self-signed certificates (unsafe for production)
imap = Net::IMAP.new('imap.example.com', 993, ssl: {
  verify_mode: OpenSSL::SSL::VERIFY_NONE
})

# Custom certificate verification
imap = Net::IMAP.new('imap.example.com', 993, ssl: {
  verify_mode: OpenSSL::SSL::VERIFY_PEER,
  ca_file: '/path/to/ca-bundle.crt',
  verify_hostname: true
})

Message content security extends beyond transport encryption. Email headers can be forged, making sender addresses unreliable for security decisions. SPF (Sender Policy Framework) verifies that sending servers are authorized by the domain owner. DKIM (DomainKeys Identified Mail) cryptographically signs messages to prove authenticity and detect tampering. DMARC (Domain-based Message Authentication, Reporting, and Conformance) specifies policies for handling messages that fail SPF or DKIM checks.

Rate limiting protects against abuse through excessive connection attempts or message sending. SMTP servers implement limits on messages per connection, recipients per message, and total messages per time period. Exceeding limits triggers temporary failures or permanent blocks. Legitimate bulk senders require IP address warming and reputation management.

Practical Examples

Sending multipart MIME messages requires constructing proper headers and boundaries. The Mail gem simplifies this process compared to manual MIME assembly, but understanding the underlying structure helps diagnose issues and implement custom behavior.

require 'mail'

Mail.defaults do
  delivery_method :smtp, {
    address: 'smtp.example.com',
    port: 587,
    user_name: 'user',
    password: 'password',
    authentication: :login,
    enable_starttls_auto: true
  }
end

mail = Mail.new do
  from 'sender@example.com'
  to 'recipient@example.com'
  subject 'Report with Attachment'
  
  text_part do
    body 'Please review the attached report.'
  end
  
  html_part do
    content_type 'text/html; charset=UTF-8'
    body '<p>Please review the <strong>attached report</strong>.</p>'
  end
  
  add_file filename: 'report.pdf', content: File.read('report.pdf')
end

mail.deliver

Retrieving messages with IMAP and processing MIME structure demonstrates parsing multipart content. Messages contain nested MIME parts that require recursive traversal. Each part has its own content type and encoding.

require 'net/imap'
require 'mail'

imap = Net::IMAP.new('imap.example.com', 993, true)
imap.login('user', 'password')
imap.select('INBOX')

# Fetch recent messages
message_ids = imap.search(['SINCE', '1-Jan-2024'])

message_ids.first(10).each do |msg_id|
  # Fetch full message content
  fetch_data = imap.fetch(msg_id, 'RFC822').first
  raw_message = fetch_data.attr['RFC822']
  
  # Parse with Mail gem
  mail = Mail.read_from_string(raw_message)
  
  puts "Subject: #{mail.subject}"
  puts "From: #{mail.from.first}"
  puts "Date: #{mail.date}"
  
  # Extract text content
  if mail.multipart?
    text_part = mail.text_part
    html_part = mail.html_part
    
    puts "Text body:" if text_part
    puts text_part.decoded if text_part
    
    puts "HTML body:" if html_part
    puts html_part.decoded if html_part
    
    # Process attachments
    mail.attachments.each do |attachment|
      filename = attachment.filename
      puts "Attachment: #{filename} (#{attachment.content_type})"
      
      File.write("downloads/#{filename}", attachment.decoded)
    end
  else
    puts mail.decoded
  end
  
  # Mark as read
  imap.store(msg_id, '+FLAGS', [:Seen])
end

imap.logout

Implementing a mail queue processor with POP3 demonstrates production patterns for reliable message handling. The processor retrieves messages, processes them asynchronously, and removes them only after successful handling.

require 'net/pop'

class MailQueueProcessor
  def initialize(host, port, username, password)
    @host = host
    @port = port
    @username = username
    @password = password
  end
  
  def process_queue
    Net::POP3.start(@host, @port, @username, @password) do |pop|
      pop.mails.each do |message|
        begin
          raw_content = message.pop
          mail = Mail.read_from_string(raw_content)
          
          # Process message
          handle_message(mail)
          
          # Archive before deletion
          archive_message(mail)
          
          # Mark for deletion only after successful processing
          message.delete
          
        rescue StandardError => e
          # Log error but don't delete message
          log_error(message.number, e)
        end
      end
    end
  end
  
  private
  
  def handle_message(mail)
    case mail.subject
    when /\[SUPPORT\]/
      create_support_ticket(mail)
    when /\[ORDER\]/
      process_order(mail)
    else
      route_to_default(mail)
    end
  end
  
  def archive_message(mail)
    Archive.create!(
      message_id: mail.message_id,
      from: mail.from.first,
      subject: mail.subject,
      received_at: mail.date,
      raw_content: mail.to_s
    )
  end
  
  def log_error(message_number, error)
    logger.error("Failed processing message #{message_number}: #{error.message}")
    logger.error(error.backtrace.join("\n"))
  end
end

Managing IMAP folders and organizing messages demonstrates server-side mail management. This approach keeps messages on the server while organizing them into folders based on content or sender.

require 'net/imap'

class ImapOrganizer
  def initialize(host, username, password)
    @imap = Net::IMAP.new(host, 993, true)
    @imap.login(username, password)
  end
  
  def organize_inbox
    @imap.select('INBOX')
    
    # Create folder structure if needed
    ensure_folders(['Work', 'Personal', 'Receipts', 'Newsletters'])
    
    # Get all unprocessed messages
    message_ids = @imap.search(['UNKEYWORD', 'Processed'])
    
    message_ids.each do |msg_id|
      envelope = @imap.fetch(msg_id, 'ENVELOPE').first.attr['ENVELOPE']
      from = "#{envelope.from[0].mailbox}@#{envelope.from[0].host}"
      subject = envelope.subject
      
      destination = categorize(from, subject)
      
      if destination
        @imap.copy(msg_id, destination)
        @imap.store(msg_id, '+FLAGS', [:Deleted])
      end
      
      # Mark as processed regardless
      @imap.store(msg_id, '+FLAGS', ['Processed'])
    end
    
    # Expunge deleted messages
    @imap.expunge
  end
  
  private
  
  def ensure_folders(folders)
    existing = @imap.list('', '*').map(&:name)
    
    folders.each do |folder|
      unless existing.include?(folder)
        @imap.create(folder)
        @imap.subscribe(folder)
      end
    end
  end
  
  def categorize(from, subject)
    return 'Receipts' if subject =~ /receipt|invoice|order confirmation/i
    return 'Newsletters' if from =~ /@newsletter|@marketing/
    return 'Work' if from =~ /@company\.com$/
    return 'Personal' if from =~ /family|friend/i
    nil
  end
  
  def close
    @imap.logout
  end
end

Tools & Ecosystem

The Mail gem provides high-level abstractions over the Net libraries, handling MIME multipart construction, encoding, and parsing. It offers a delivery method abstraction that supports SMTP, Sendmail, and test modes. The gem integrates with Rails as ActionMailer's underlying engine.

# Mail gem with custom delivery settings
require 'mail'

mail = Mail.new do
  from 'sender@example.com'
  to 'recipient@example.com'
  subject 'Test Message'
  body 'Message content'
  
  delivery_method :smtp, {
    address: 'smtp.example.com',
    port: 587,
    domain: 'example.com',
    user_name: 'user',
    password: 'password',
    authentication: :login,
    enable_starttls_auto: true,
    openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER
  }
end

mail.deliver!

Mailman processes incoming mail through Rack-compatible handlers or direct POP3/IMAP polling. The gem routes messages to Ruby methods based on sender, recipient, or content patterns. It handles background polling and automatic reconnection.

require 'mailman'

Mailman.config.pop3 = {
  server: 'pop.example.com',
  port: 995,
  ssl: true,
  username: 'user',
  password: 'password'
}

Mailman::Application.run do
  to 'support@example.com' do
    SupportTicket.create!(
      from: message.from.first,
      subject: message.subject,
      body: message.body.decoded
    )
  end
  
  from 'boss@example.com' do
    # High priority routing
    UrgentQueue.push(message)
  end
  
  subject /\[ORDER-(\d+)\]/ do
    order_id = params['captures'].first
    Order.find(order_id).process_email(message)
  end
  
  default do
    DefaultHandler.process(message)
  end
end

The gmail gem wraps Gmail's IMAP extensions, providing simplified access to labels, threads, and Gmail-specific features. It handles OAuth2 authentication and Gmail's unique folder structure.

require 'gmail'

gmail = Gmail.connect('user@gmail.com', 'password')

# Use Gmail labels
unread = gmail.inbox.emails(:unread)
unread.each do |email|
  puts email.subject
  email.label!('Processed')
  email.mark(:read)
end

# Search with Gmail syntax
important = gmail.mailbox('[Gmail]/Important').emails
starred = gmail.mailbox('[Gmail]/Starred').emails

# Thread handling
gmail.inbox.emails.each do |email|
  thread = email.thread
  puts "Thread with #{thread.count} messages"
end

gmail.logout

Pony provides a minimalist interface for sending mail with automatic SMTP configuration detection. It suits simple use cases where full Mail gem features are unnecessary.

require 'pony'

Pony.mail(
  to: 'recipient@example.com',
  from: 'sender@example.com',
  subject: 'Quick Message',
  body: 'Message content',
  via: :smtp,
  via_options: {
    address: 'smtp.example.com',
    port: 587,
    enable_starttls_auto: true,
    user_name: 'user',
    password: 'password',
    authentication: :login
  }
)

ActionMailer integrates email functionality into Rails applications, providing mailer classes, view templates, and delivery configuration. It uses the Mail gem internally while adding Rails conventions and features.

Premailer inlines CSS styles for email HTML, converting stylesheet rules to inline style attributes. Email clients have inconsistent CSS support, requiring inline styles for reliable rendering.

LetterOpener intercepts outgoing mail in development environments, opening messages in the browser instead of sending them. This prevents accidental message delivery during testing and development.

Common Pitfalls

SMTP delivery failures often occur silently. The Net::SMTP methods return successfully after the receiving server accepts the message, but subsequent delivery failures generate bounce messages sent to the envelope sender. Applications must monitor bounce addresses and process delivery status notifications.

# Setting a bounce handler address
smtp.mailfrom('sender@example.com', 'ENVID=uniqueid123')
smtp.rcptto('recipient@example.com', 'NOTIFY=SUCCESS,FAILURE')

# But bounces arrive asynchronously as email messages
# Requires separate processing of bounce address

Line ending conventions differ between platforms and protocols. Email uses CRLF (carriage return, line feed) line endings per RFC 5322. Ruby's string literals use LF (line feed) on Unix systems. Mixing line endings breaks message parsing and causes protocol violations. The Net::SMTP library handles conversion automatically, but manual message construction requires attention to line endings.

# Incorrect: Unix line endings
message = "From: sender@example.com\nTo: recipient@example.com\n\nBody"

# Correct: CRLF line endings
message = "From: sender@example.com\r\nTo: recipient@example.com\r\n\r\nBody"

# Or use heredoc with explicit conversion
message = <<~MSG.gsub("\n", "\r\n")
  From: sender@example.com
  To: recipient@example.com
  
  Body
MSG

POP3's exclusive locking prevents concurrent access to mailboxes. Multiple processes attempting to connect to the same POP3 account generate errors. This limitation makes POP3 unsuitable for distributed processing or web applications with multiple workers. IMAP supports concurrent access, making it preferable for multi-client scenarios.

IMAP sequence numbers change as messages are deleted or moved. Code that stores sequence numbers between operations experiences incorrect message references. UIDs remain stable and should be used for persistent references. However, UIDs become invalid if the mailbox's UIDVALIDITY value changes, which occurs during mailbox reconstruction.

# Wrong: Sequence numbers change
imap.select('INBOX')
messages = imap.search(['ALL'])
# => [1, 2, 3, 4, 5]

# Later, after some messages deleted
imap.fetch(3, 'BODY[]')  # Fetches wrong message!

# Correct: Use UIDs
messages = imap.uid_search(['ALL'])
# => [101, 102, 103, 104, 105]

# UIDs remain stable
imap.uid_fetch(103, 'BODY[]')  # Always correct message

Character encoding issues arise from legacy email systems using various character sets. Headers should use ASCII with encoded-words for non-ASCII characters. Message bodies declare encoding through Content-Type charset parameters. The Mail gem handles encoding automatically, but manual processing requires explicit encoding conversion.

Large message handling causes memory issues with naive implementations. Loading entire mailboxes into memory fails with large archives. IMAP partial fetch retrieves byte ranges, enabling streaming or selective content access. POP3 lacks partial fetch, requiring full message retrieval.

# Memory-intensive approach
imap.select('Archive')
all_messages = imap.search(['ALL'])
all_messages.each do |msg_id|
  full_message = imap.fetch(msg_id, 'RFC822').first
  # Loads all messages into memory
end

# Memory-efficient approach
imap.select('Archive')
batch_size = 100
offset = 0

loop do
  batch = imap.search(['ALL']).slice(offset, batch_size)
  break if batch.empty?
  
  batch.each do |msg_id|
    # Process one at a time
    message_data = imap.fetch(msg_id, 'RFC822').first
    process_message(message_data)
  end
  
  offset += batch_size
end

Connection timeouts occur with slow servers or network issues. The Net libraries use Ruby's default socket timeout, which may be too short for slow mail servers. Setting explicit timeouts prevents hanging connections but requires handling timeout exceptions.

Authentication failures stem from incorrect credentials, disabled accounts, or security restrictions. Gmail and other providers require application-specific passwords or OAuth2 for third-party applications. Regular password authentication may be disabled for security reasons.

SMTP relay restrictions prevent unauthorized mail sending. Most SMTP servers require authentication before accepting mail for non-local domains. Attempting to send without authentication results in relay access denied errors. Port 25 blocks are common on residential ISPs to prevent spam, requiring use of submission ports 587 or 465.

Reference

Protocol Comparison

Protocol Purpose Default Ports State Location Multi-Client
SMTP Send messages 25, 587, 465 Stateless N/A
POP3 Download messages 110, 995 Server Exclusive lock
IMAP Access messages 143, 993 Server Concurrent

SMTP Commands

Command Purpose Syntax
HELO Identify client HELO domain
EHLO Extended hello EHLO domain
MAIL FROM Set envelope sender MAIL FROM:
RCPT TO Add recipient RCPT TO:
DATA Begin message content DATA
RSET Reset transaction RSET
QUIT End session QUIT
STARTTLS Upgrade to TLS STARTTLS

POP3 Commands

Command Purpose Syntax
USER Specify username USER name
PASS Provide password PASS string
STAT Get message count STAT
LIST List message sizes LIST [msg]
RETR Retrieve message RETR msg
DELE Mark for deletion DELE msg
RSET Unmark deletions RSET
QUIT End session QUIT
TOP Get headers + lines TOP msg n

IMAP Common Commands

Command Purpose Syntax
LOGIN Authenticate LOGIN user pass
SELECT Open mailbox SELECT mailbox
EXAMINE Open read-only EXAMINE mailbox
CREATE Create mailbox CREATE mailbox
DELETE Delete mailbox DELETE mailbox
RENAME Rename mailbox RENAME old new
LIST List mailboxes LIST reference name
LSUB List subscribed LSUB reference name
SEARCH Find messages SEARCH criteria
FETCH Get message data FETCH set items
STORE Set flags STORE set +FLAGS flags
COPY Copy messages COPY set mailbox
EXPUNGE Delete marked EXPUNGE
CLOSE Close mailbox CLOSE
LOGOUT End session LOGOUT

IMAP Search Criteria

Criterion Matches Example
ALL All messages SEARCH ALL
ANSWERED Has Answered flag SEARCH ANSWERED
DELETED Has Deleted flag SEARCH DELETED
FLAGGED Has Flagged flag SEARCH FLAGGED
NEW Recent and unseen SEARCH NEW
OLD Not recent SEARCH OLD
RECENT Has Recent flag SEARCH RECENT
SEEN Has Seen flag SEARCH SEEN
UNANSWERED No Answered flag SEARCH UNANSWERED
UNDELETED No Deleted flag SEARCH UNDELETED
UNFLAGGED No Flagged flag SEARCH UNFLAGGED
UNSEEN No Seen flag SEARCH UNSEEN
FROM From address SEARCH FROM alice
TO To address SEARCH TO bob
SUBJECT Subject text SEARCH SUBJECT report
BODY Body text SEARCH BODY urgent
BEFORE Internal date before SEARCH BEFORE 1-Jan-2024
SINCE Internal date since SEARCH SINCE 1-Jan-2024
LARGER Size larger than SEARCH LARGER 10000
SMALLER Size smaller than SEARCH SMALLER 1000
OR Either criterion SEARCH OR FROM alice TO bob
NOT Inverted criterion SEARCH NOT SEEN

Ruby Net::SMTP Methods

Method Purpose Returns
new(address, port) Create instance Net::SMTP
start(helo, user, pass, authtype) Begin session Block result or nil
send_message(msg, from, to) Send complete message Response
mailfrom(address) Set sender Response
rcptto(address) Add recipient Response
data Transmit message Block result
finish Close session Response
enable_starttls_auto Enable STARTTLS nil
enable_tls Enable implicit TLS nil

Ruby Net::POP3 Methods

Method Purpose Returns
new(address, port) Create instance Net::POP3
start(user, pass) Begin session Block result or nil
mails Get message list Array of POPMail
n_mails Message count Integer
n_bytes Total bytes Integer
finish Close session nil
enable_ssl Enable TLS nil

Ruby Net::IMAP Methods

Method Purpose Returns
new(host, port, ssl) Create connection Net::IMAP
login(user, pass) Authenticate Response
select(mailbox) Open mailbox MailboxData
search(criteria) Find messages Array of integers
uid_search(criteria) Find by UID Array of integers
fetch(set, attr) Get message data Array of FetchData
uid_fetch(set, attr) Fetch by UID Array of FetchData
store(set, attr, flags) Set flags Array of FetchData
copy(set, mailbox) Copy messages Response
expunge Permanently delete Response
logout End session Response

Message Flags

Flag Meaning Set By
Seen Message read Client marking read
Answered Reply sent Client sending reply
Flagged Marked important User action
Deleted Marked for deletion User or filter
Draft Incomplete message Message composition
Recent Newly arrived Server on delivery

Common MIME Types

Type Description Usage
text/plain Plain text Message bodies
text/html HTML content Rich message bodies
multipart/alternative Alternative versions Text and HTML together
multipart/mixed Mixed content Messages with attachments
multipart/related Related parts HTML with embedded images
application/pdf PDF document Document attachments
image/jpeg JPEG image Photo attachments
application/octet-stream Binary data Generic files

TLS Configuration

Setting Purpose Values
enable_starttls_auto Upgrade if available Boolean
enable_starttls Required upgrade Boolean
enable_tls Implicit TLS Boolean
verify_mode Certificate validation VERIFY_NONE, VERIFY_PEER
ca_file CA certificate path File path
cert Client certificate OpenSSL::X509::Certificate
key Client private key OpenSSL::PKey::RSA