Skip to content

Ruby Sending Email - SMTP

In modern applications, sending emails is a common requirement, such as user registration confirmation, password reset, notifications, etc. Ruby provides multiple ways to send emails, with the most common being through the SMTP (Simple Mail Transfer Protocol) protocol. This chapter will explain in detail how to send emails using SMTP in Ruby.

🎯 SMTP Basics

What is SMTP

SMTP (Simple Mail Transfer Protocol) is the standard protocol for sending emails. It defines how mail servers and clients transfer emails between each other. In Ruby, we can use the built-in [Net::SMTP](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb#L89-L774) library to send emails.

How SMTP Works

The basic process of sending emails via SMTP:

  1. Connect to SMTP server
  2. Authenticate (if required)
  3. Specify sender and recipient
  4. Send email content
  5. Close connection

📧 Using Net::SMTP to Send Emails

Basic Email Sending

ruby
require 'net/smtp'

# SMTP server configuration
smtp_server = 'smtp.gmail.com'
port = 587
username = 'your_email@gmail.com'
password = 'your_password'

# Email content
from = 'your_email@gmail.com'
to = 'recipient@example.com'
subject = 'Test Email'
body = "This is a test email\n\nFrom Ruby application"

# Create email message
message = <<~MESSAGE
  From: #{from}
  To: #{to}
  Subject: #{subject}
  
  #{body}
MESSAGE

# Send email
Net::SMTP.start(smtp_server, port, 'localhost', username, password, :plain) do |smtp|
  smtp.send_message(message, from, to)
end

puts "Email sent successfully!"

Sending HTML Emails

ruby
require 'net/smtp'

def send_html_email(smtp_server, port, username, password, from, to, subject, html_body)
  # Create HTML email message
  message = <<~MESSAGE
    From: #{from}
    To: #{to}
    Subject: #{subject}
    Content-Type: text/html; charset=UTF-8
    
    #{html_body}
  MESSAGE
  
  # Send email
  Net::SMTP.start(smtp_server, port, 'localhost', username, password, :plain) do |smtp|
    smtp.send_message(message, from, to)
  end
end

# Usage example
smtp_server = 'smtp.gmail.com'
port = 587
username = 'your_email@gmail.com'
password = 'your_password'
from = 'your_email@gmail.com'
to = 'recipient@example.com'
subject = 'HTML Test Email'

html_body = <<~HTML
  <html>
    <body>
      <h1>Welcome to our service</h1>
      <p>This is an HTML formatted email</p>
      <ul>
        <li>Feature 1</li>
        <li>Feature 2</li>
        <li>Feature 3</li>
      </ul>
      <p>Thank you for using our service!</p>
    </body>
  </html>
HTML

send_html_email(smtp_server, port, username, password, from, to, subject, html_body)
puts "HTML email sent successfully!"

Sending Emails with Attachments

ruby
require 'net/smtp'
require 'base64'

def send_email_with_attachment(smtp_server, port, username, password, from, to, subject, body, attachment_path)
  # Read attachment
  filename = File.basename(attachment_path)
  file_content = File.read(attachment_path, mode: 'rb')
  encoded_content = Base64.encode64(file_content).gsub(/\n/, "\n")

  # Create email with attachment
  boundary = "----=_NextPart_#{Time.now.to_i}_#{rand(1000000)}"

  message = <<~MESSAGE
    From: #{from}
    To: #{to}
    Subject: #{subject}
    MIME-Version: 1.0
    Content-Type: multipart/mixed; boundary="#{boundary}"
    
    --#{boundary}
    Content-Type: text/plain; charset=UTF-8
    
    #{body}
    
    --#{boundary}
    Content-Type: application/octet-stream; name="#{filename}"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename="#{filename}"
    
    #{encoded_content}
    --#{boundary}--
  MESSAGE

  # Send email
  Net::SMTP.start(smtp_server, port, 'localhost', username, password, :plain) do |smtp|
    smtp.send_message(message, from, to)
  end
end

# Usage example
smtp_server = 'smtp.gmail.com'
port = 587
username = 'your_email@gmail.com'
password = 'your_password'
from = 'your_email@gmail.com'
to = 'recipient@example.com'
subject = 'Email with Attachment'
body = "This is an email with attachment\n\nPlease check the attachment."
attachment_path = 'path/to/your/file.pdf'

# send_email_with_attachment(smtp_server, port, username, password, from, to, subject, body, attachment_path)
puts "Email with attachment sent successfully!"

🛠️ Email Configuration Management

Configuration Class

ruby
class EmailConfig
  attr_accessor :smtp_server, :port, :username, :password, :domain
  
  def initialize(smtp_server, port, username, password, domain = 'localhost')
    @smtp_server = smtp_server
    @port = port
    @username = username
    @password = password
    @domain = domain
  end
  
  # Common email service provider configurations
  def self.gmail(username, password)
    new('smtp.gmail.com', 587, username, password, 'localhost')
  end
  
  def self.outlook(username, password)
    new('smtp-mail.outlook.com', 587, username, password, 'localhost')
  end
  
  def self.yahoo(username, password)
    new('smtp.mail.yahoo.com', 587, username, password, 'localhost')
  end
  
  def self.qq(username, password)
    new('smtp.qq.com', 587, username, password, 'localhost')
  end
end

# Usage example
gmail_config = EmailConfig.gmail('your_email@gmail.com', 'your_password')
puts gmail_config.smtp_server  # smtp.gmail.com
puts gmail_config.port         # 587

Email Sender Class

ruby
require 'net/smtp'
require 'base64'

class EmailSender
  def initialize(config)
    @config = config
  end
  
  def send_email(to, subject, body, options = {})
    from = options[:from] || @config.username
    content_type = options[:content_type] || 'text/plain'
    attachments = options[:attachments] || []
    
    message = build_message(from, to, subject, body, content_type, attachments)
    
    Net::SMTP.start(
      @config.smtp_server,
      @config.port,
      @config.domain,
      @config.username,
      @config.password,
      :plain
    ) do |smtp|
      smtp.send_message(message, from, to)
    end
  end
  
  private
  
  def build_message(from, to, subject, body, content_type, attachments)
    if attachments.empty?
      build_simple_message(from, to, subject, body, content_type)
    else
      build_multipart_message(from, to, subject, body, content_type, attachments)
    end
  end
  
  def build_simple_message(from, to, subject, body, content_type)
    <<~MESSAGE
      From: #{from}
      To: #{to}
      Subject: #{subject}
      Content-Type: #{content_type}; charset=UTF-8
      
      #{body}
    MESSAGE
  end
  
  def build_multipart_message(from, to, subject, body, content_type, attachments)
    boundary = "----=_NextPart_#{Time.now.to_i}_#{rand(1000000)}"
    
    message = <<~MESSAGE
      From: #{from}
      To: #{to}
      Subject: #{subject}
      MIME-Version: 1.0
      Content-Type: multipart/mixed; boundary="#{boundary}"
      
      --#{boundary}
      Content-Type: #{content_type}; charset=UTF-8
      
      #{body}
    MESSAGE
    
    attachments.each do |attachment_path|
      filename = File.basename(attachment_path)
      file_content = File.read(attachment_path, mode: 'rb')
      encoded_content = Base64.encode64(file_content).gsub(/\n/, "\n")
      
      message += <<~ATTACHMENT
      
        --#{boundary}
        Content-Type: application/octet-stream; name="#{filename}"
        Content-Transfer-Encoding: base64
        Content-Disposition: attachment; filename="#{filename}"
        
        #{encoded_content}
      ATTACHMENT
    end
    
    message + "\n--#{boundary}--"
  end
end

# Usage example
config = EmailConfig.gmail('your_email@gmail.com', 'your_password')
sender = EmailSender.new(config)

# Send simple email
sender.send_email(
  'recipient@example.com',
  'Test Email',
  'This is a test email'
)

# Send HTML email
sender.send_email(
  'recipient@example.com',
  'HTML Test',
  '<h1>Hello</h1><p>This is HTML</p>',
  content_type: 'text/html'
)

# Send email with attachment
sender.send_email(
  'recipient@example.com',
  'With Attachment',
  'Please check the attachment',
  attachments: ['/path/to/file.pdf']
)

🔒 Email Sending Best Practices

1. Use Environment Variables for Credentials

ruby
class SecureEmailConfig
  def self.smtp_settings
    {
      address: ENV['SMTP_ADDRESS'] || 'smtp.gmail.com',
      port: ENV['SMTP_PORT'] || 587,
      username: ENV['SMTP_USERNAME'],
      password: ENV['SMTP_PASSWORD'],
      authentication: :plain,
      enable_starttls_auto: true
    }
  end
end

2. Handle Failures Gracefully

ruby
class ReliableEmailSender
  def initialize(config)
    @config = config
    @max_retries = 3
  end

  def send_with_retry(to, subject, body, options = {})
    retries = 0
    
    begin
      send_email(to, subject, body, options)
      true
    rescue => e
      retries += 1
      if retries < @max_retries
        sleep(2 ** retries)  # Exponential backoff
        retry
      else
        log_failure(to, subject, e)
        false
      end
    end
  end

  private

  def send_email(to, subject, body, options)
    # Email sending logic
  end

  def log_failure(to, subject, error)
    # Log the failure
  end
end

3. Template-Based Emails

ruby
class TemplateEmailSender
  TEMPLATES = {
    welcome: {
      subject: 'Welcome to Our Service!',
      html_template: '<h1>Welcome, %s!</h1><p>Thank you for joining us.</p>'
    },
    password_reset: {
      subject: 'Password Reset Request',
      html_template: '<h1>Password Reset</h1><p>Click <a href="%s">here</a> to reset your password.</p>'
    }
  }

  def send_template_email(template_name, to, template_params = {})
    template = TEMPLATES[template_name]
    return unless template

    subject = template[:subject]
    html_body = sprintf(template[:html_template], *template_params.values)

    EmailSender.new(@config).send_email(to, subject, html_body, content_type: 'text/html')
  end
end

📚 Next Steps

After mastering Ruby SMTP email sending, we recommend continuing to learn:

Continue your Ruby learning journey!

Content is for learning and research only.