Sending Emails with Ruby

On July 17, 2021
11min read
Maciej Walusiak Full Stack Developer @Railsware

Let’s say you have a working Ruby app and need to add an email delivery functionality to it. This could be related to user authentication, or any other kind of transactional emails, it makes no difference. This tutorial is tailored to help you implement sending emails with Ruby and introduce versatile options for that. 

Options to send an email in Ruby

Mostly, you can pick one of three options. 

The simplest one is using Net::SMTP class. It provides the functionality to send email via SMTP. The drawback of this option is that Net::SMTP lacks functions to compose emails. You can always create them yourself, but this takes time.

The second option is to use a dedicated Ruby gem like Mail, Pony, or others. These solutions let you handle email activities in a simple and effective way. Action Mailer is a perfect email solution through the prism of Rails. And, most likely, this will be your choice. 

The third option is class Socket. Mostly, this class allows you to set communication between processes or within a process. So, email sending can be implemented with it as well. However, the truth is that Socket does not provide you with extensive functionalities, and you’re unlikely to want to go with it. 

Now, let’s try to send an email using each of the described solutions. 

How to send emails in Ruby via Net::SMTP

From our experience, no one would use that option in a regular web app. However, sending emails via Net::SMTP could be a fit if you use mruby (a lightweight implementation of the Ruby language) on some IoT device. Also, it will do if used in serverless computing, for example, AWS Lambda. Check out this script example first and then we’ll go through it in detail.

require 'net/smtp'

message = <<END_OF_MESSAGE
From: YourRubyApp <info@yourrubyapp.com>
To: BestUserEver <your@bestuserever.com>
Subject: Any email subject you want
Date: Tue, 02 Jul 2019 15:00:34 +0800

Lorem Ipsum
END_OF_MESSAGE
Net::SMTP.start('your.smtp.server', 25) do |smtp|
  smtp.send_message message,
  'info@yourrubyapp.com',
  'your@bestuserever.com'
end

This is a simple example of sending a textual email via SMTP (official documentation can be found here). You can see four headers: From, To, Subject, and Date. Keep in mind that you have to separate them with a blank line from the email body text. Equally important is to connect to the SMTP server. 

Net::SMTP.start('your.smtp.server', 25) do |smtp|

Naturally, here will appear your data instead of ‘your.smtp.server‘, and 25 is a default port number. If needed, you can specify other details like username, password, or authentication scheme (:plain, :login, and :cram_md5). It may look as follows:

Net::SMTP.start('your.smtp.server', 25, ‘localhost’, ‘username’, ‘password’ :plain) do |smtp|

Here, you will connect to the SMTP server using a username and password in plain text format, and the client’s hostname will be identified as localhost.

After that, you can use the send_message method and specify the addresses of the sender and the recipient as parameters. The block form of SMTP.start (Net::SMTP.start('your.smtp.server', 25) do |smtp|) closes the SMTP session automatically.

Test Your Emails Now

In the Ruby Cookbook, sending emails with the Net::SMTP library is referred to as minimalism since you have to build the email string manually. Nevertheless, it’s not as hopeless as you may think of. Let’s see how you can enhance your email with HTML content and even add an attachment.

Sending an HTML email in Net::SMTP

Check out this script example that refers to the message section.

message = <<END_OF_MESSAGE
From: YourRubyApp <info@yourrubyapp.com>
To: BestUserEver <your@bestuserever.com>
MIME-Version: 1.0
Content-type: text/html
Subject: Any email subject you want
Date: Tue, 02 Jul 2019 15:00:34 +0800

A bit of plain text.

<strong>The beginning of your HTML content.</strong>
<h1>And some headline, as well.</h1>
END_OF_MESSAGE

Apart from HTML tags in the message body, we’ve got two additional headers: MIME-Version and Content-type.  MIME refers to Multipurpose Internet Mail Extensions. It is an extension to Internet email protocol that allows you to combine different content types in a single message body. The value of MIME-Version is typically 1.0. It indicates that a message is MIME-formatted. 

As for the Content-type header, everything is clear. In our case, we have two types – HTML and plain text. Also, make sure to separate these content types using defining boundaries. 

Except for MIME-Version and Content-type, you can use other MIME headers:

  • Content-Disposition – specifies the presentation style (inline or attachment)
  • Content-Transfer-Encoding – indicates a binary-to-text encoding scheme (7bit, quoted-printable, base64, 8bit, or binary).

We’ll check them out in one of the following examples.

Sending an email with an attachment in Net::SMTP 

Let’s add an attachment, such as a PDF file. In this case, we need to update Content-type to multipart/mixed. Also, use the pack("m") function to encode the attached file with base64 encoding.

require 'net/smtp'

filename = "/tmp/Attachment.pdf"
file_content = File.read(filename)
encoded_content = [file_content].pack("m")   # base64

marker = "AUNIQUEMARKER"

After that, you need to define three parts of your email.

Part 1 – Main headers

part1 = <<END_OF_MESSAGE
From: YourRubyApp <info@yourrubyapp.com>
To: BestUserEver <your@bestuserever.com>
Subject: Adding attachment to email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary = #{marker}
--#{marker}
END_OF_MESSAGE

Part 2 – Message action

part2 = <<END_OF_MESSAGE
Content-Type: text/html
Content-Transfer-Encoding:8bit

A bit of plain text.

<strong>The beginning of your HTML content.</strong>
<h1>And some headline, as well.</h1>
--#{marker}
END_OF_MESSAGE

Part 3 – Attachment

part3 = <<END_OF_MESSAGE
Content-Type: multipart/mixed; name = "#{filename}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename = "#{filename}"

#{encoded_content}
--#{marker}--
END_OF_MESSAGE

Now, we can put all the parts together and finalize the script. That’s how it will look:

require 'net/smtp'

filename = "/tmp/Attachment.pdf"
file_content = File.read(filename)
encoded_content = [file_content].pack("m")   # base64

marker = "AUNIQUEMARKER"

part1 = <<END_OF_MESSAGE
From: YourRubyApp <info@yourrubyapp.com>
To: BestUserEver <your@bestuserever.com>
Subject: Adding attachment to email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary = #{marker}
--#{marker}
END_OF_MESSAGE

part2 = <<END_OF_MESSAGE
Content-Type: text/html
Content-Transfer-Encoding:8bit

A bit of plain text.

<strong>The beginning of your HTML content.</strong>
<h1>And some headline, as well.</h1>
--#{marker}
END_OF_MESSAGE

part3 = <<END_OF_MESSAGE
Content-Type: multipart/mixed; name = "#{filename}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename = "#{filename}"

#{encoded_content}
--#{marker}--
END_OF_MESSAGE

message = part1 + part2 + part3

begin
  Net::SMTP.start('your.smtp.server', 25) do |smtp|
    smtp.send_message message,
    'info@yourrubyapp.com',
    'your@bestuserever.com'
  end

Can I send an email to multiple recipients in Net::SMTP?

Definitely, you can. send_message expects second and subsequent arguments to contain recipients’ emails. For example, like this:

Net::SMTP.start('your.smtp.server', 25) do |smtp|
  smtp.send_message message,
  'info@yourrubyapp.com',
  'your@bestuserever1.com',
  ‘your@bestuserever2.com’,
  ‘your@bestuserever3.com
end

Best Ruby gems for sending emails

In Ruby ecosystem, you can find specific email gems that can improve your email sending experience.

Ruby Mail

This library is aimed at giving a single point of access to manage all email-related activities including sending and receiving email. To get started with Mail, execute require ‘mail’

A simple email looks as follows:

mail = Mail.new do
  from    'info@yourrubyapp.com'
  to      'your@bestuserever.com'
  subject 'Any subject you want'
  body    'Lorem Ipsum'
end

SMTP is a default mail delivery method with a local host port 25. You can change SMTP settings:

Mail.defaults do
  delivery_method :smtp, address: "localhost", port: 1025
end

or even the delivery method:

mail.delivery_method :sendmail

mail.deliver

Sending an email with HTML and attachment in Ruby Mail

You won’t have any troubles with building a multipart email. Check out this example:

mail = Mail.new do
  from    'info@yourrubyapp.com'
  to      'your@bestuserever.com'
  subject 'Email with HTML and an attachment'

  text_part do
    body 'Put your plain text here'
  end

  html_part do
    content_type 'text/html; charset=UTF-8'
    body '<h1>And here is the place for HTML</h1>'
  end
  add_file '/path/to/Attachment.pdf'
end
end

That’s a brief overview of Ruby Mail for email sending. You can read more about this library here.

Pony

You might have heard a fairy tale about sending an email in one command. Hold on to your hats, cause it’s real and provided by Pony gem. 

Pony.mail(:to => 'your@bestuserever.com', :from => 'info@yourrubyapp.com', :subject => 'Any subject you want', :body => 'Lorem Ipsum')

Actually, you can cut this script to having only :to option key if necessary. Also, it is possible to specify a delivery method: 

Pony.mail(:to => 'your@bestuserever.com', :via => :smtp)

or

Pony.mail(:to => 'your@bestuserever.com', :via => :sendmail)

And here’s how you can configure your SMTP server in Pony: 

Pony.mail(:to => 'your@bestuserever.com', :via => :smtp, :via_options => {
  :address        => 'smtp.yourserver.com',
  :port           => '25',
  :user_name      => 'user',
  :password       => 'pass',
  :authentication => :plain,
  :domain         => "yourrubyapp.com"
})

Authorization options include plain, login, and cram_md5. By default, no authorization is set. 

Unfortunately, such an ascetic way of sending emails excludes any possibility to add an attachment or HTMLize your message. 

ActionMailer

This is the most popular gem for sending emails on Rails. In case your app is written on top of it, ActionMailer will certainly come up. It lets you send emails using mailer classes and views. Here’s how a simple email built with ActionMailer may look:

class TestMailer < ActionMailer::Base
  default from: 'info@yourrubyapp.com'
  def simple_message(recipient)
    mail(
      to: recipient,
      subject: 'Any subject you want',
      body: 'Lorem Ipsum'
    )
  end
end

How to send emails with ActionMailer

At RailsGuides, you’ll find a detailed tutorial on how to use ActionMailer at best. What we need is to create and send an email. So, make use of this step-by-step guide. 

Create a mailer model

Your app will send emails using a mailer model and views. So, you need to create them first. 

$ rails generate mailer Notifier

To generate an email message we need helpers that are defined in a mailer model. You can set up variables in the mailer views, options on the mail like :to address, as well as attachments. Also, different helpers are available to:

  • add attachments: attachments['filename.png'] = File.read('path/to/attachment.pdf')
  • add an inline attachment: attachments.inline['filename.png'] = File.read('path/to/attachment.pdf')
  • specify a header field: headers['X-No-Spam'] = 'True'
  • specify multiple headers: headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})
  • specify the email to be sent: mail 

Here’s the result:

class UserMailer< ApplicationMailer
  def simple_message(recipient)
    attachments["attachment.pdf"] = File.read("path/to/file.pdf")
    mail(
      to: “your@bestuserever.com”,
      subject: "New account information",
      content_type: "text/html",
      body: "<html><strong>Hello there</strong></html>"
    )
  end

This mailer class already includes an attachment and HTML content. Now, let’s create a corresponding view. 

Create a view

View denotes a template to be used with a mailer. You need to create an .erb file named the same as the method in the mailer class. In our case, it is new-account.html.erb. Locate it in app/views/notifier_mailer/. This template will be used for the HTML formatted emails. Also, you can make a text part for this email by creating new-account.txt.erb in the same directory. Fill both files with the relevant content. 

Server configuration

The next step is ActionMailer configuration and defining a delivery method. SMTP is set by default and you can adjust it using config.action_mailer.smtp_settings. You can pick another delivery method like sendmail, file (save emails to files), and test (save emails to ActionMailer::Base.deliveries array). Here is an example of SMTP configuration.:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address:              'smtp.yourserver.com',
  port:                 25,
  domain:               'yourrubyapp.com',
  user_name:            '<username>',
  password:             '<password>',
  authentication:       'plain',
  enable_starttls_auto: true
}

Port 25 is set by default and you can select plain, login or cram_md5 as an authentication option. If you want to use some cloud-based delivery platform like SendGrid, that’s how it’s gonna look like:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address   => 'smtp.sendgrid.net',
  :port      => 587,
  :domain    => 'sendgrid.com',
  :user_name => 'yourSendGridUsername',
  :password  => 'yourSendGridPassword',
  :authentication => 'plain',
  :enable_starttls_auto => true
}
Sending email

We’ve defined a mailer and a template and ready to deliver the message. Just call deliver_now to do it right away. Optionally, you can  defer the delivery for later with deliver_later. In Action Mailer, you need to call UserMailer.simple_message('recipient@example.com').deliver_now

Sending email to multiple recipients

Also, you can set your email to be sent to several recipients. This can be done by setting a list of email addresses to the :to key, as well as :cc, and :bcc keys. You can create the list in the form of an array of recipients’ addresses or a single string in which emails are separated by commas.

class AdminMailer < ApplicationMailer
  default to: -> { Admin.pluck(:email) },
  from: 'info@yourrubyapp.com'

  def new_registration(user)
    @user = user
    mail(subject: "New User Signup: #{@user.email}")
  end
end

Want to learn more about using the Action Mailer? Don’t hesitate to read this comprehensive introduction by Matthew Croak.

How to send emails with Ruby via Gmail

Actually, we wanted to skip this part entirely because it has no practical use case. You are able to use any aforementioned tool to configure a Gmail SMTP server and leverage it for sending emails. For example, this one is for Ruby Mail:

Mail.defaults do
  delivery_method :smtp, { :address              => "smtp.gmail.com",
    :port                 => 587,
    :domain               => 'yourrubyapp.com',
    :user_name            => '<username>',
    :password             => '<password>',
    :authentication       => 'plain',
    :enable_starttls_auto => true  }
  end

This is the Gmail SMTP server configuration for ActionMailer:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address => "smtp.gmail.com",
  :port => 587,
  :user_name => "your mail",
  :password => "your password",
  :authentication => :plain,
  :enable_starttls_auto => true
}

And, of course, one for Pony:

Pony.mail(:to => 'your@bestuserever.com', :via => :smtp, :via_options => {
  :address        => 'smtp.gmail.com',
  :port           => '587',
  :user_name      => 'user',
  :password       => 'pass',
  :authentication => :plain,
  :domain         => "yourrubyapp.com"
  })

Testing email delivery functionality 

We’ve reviewed a few ways to send emails in Ruby, but not all of them provide options to test the mail system. If you send messages using the Mail gem, you can use a bare bones mailer, TestMailer. It will test your mail system without actually sending emails like this:

require 'mail'
=> true
Mail.defaults do
  delivery_method :test
end
=> #<Mail::Configuration:0x19345a8 @delivery_method=Mail::TestMailer>
Mail::TestMailer.deliveries
=> []
Mail.deliver do
  from    'info@yourrubyapp.com'
  to      'your@bestuserever.com'
  subject 'Testing'
  body 'Lorem Ipsum'
end
=> #<Mail::Message:0x19284ec ...
Mail::TestMailer.deliveries.length
=> 1
Mail::TestMailer.deliveries.first
=> #<Mail::Message:0x19284ec ...
Mail::TestMailer.deliveries.clear
=> []

If your Ruby app sends emails via Net::SMTP or ActionMailer, you should definitely see how Mailtrap can help you with testing delivery functionality. 

Using Mailtrap to test email sending with Net::SMTP

Setup is very simple. Once you’re in your demo inbox, copy the SMTP credentials on the SMTP Settings tab and insert them in your code. Or you can get a ready-to-use template of a simple message in the Integrations section. Just choose a programming language or framework your app is built with. 

require 'net/smtp'

message = <<END_OF_MESSAGE
From: YourRubyApp <info@yourrubyapp.com>
To: BestUserEver <your@bestuserever.com>
Subject: Any email subject you want
Date: Tue, 02 Jul 2019 15:00:34 +0800

Lorem Ipsum
END_OF_MESSAGE
Net::SMTP.start('smtp.mailtrap.io', 587, '<username>', '<password>', :cram_md5) do |smtp|
  smtp.send_message message,
  'info@yourrubyapp.com',
  'your@bestuserever.com'
end

If everything is alright, you’ll see your message in the Mailtrap Demo inbox. Also, you can try to check your email with HTML content and an attachment. Mailtrap allows you to see how your email will look and check HTML if necessary.

Try Mailtrap for Free

Wrapping up

An eagle-eyed reader might have noticed that we’ve missed another option to send emails with Ruby – Socket class. And he or she would be right. We won’t bother you with details of using Socket for mailing, but we will provide you with a script sample that will likely put you off this class for emails altogether.

#!/usr/bin/ruby
require 'socket'

host = 'localhost'
port = 'smtp'
server = 'example.com'
from = 'info@yourrubyapp.com'
to = 'your@bestuserever.com'
data = "from: #{from}\nto: #{to}\nsubject: That’s your subkect.\nAnd that’s your body text."
request=''
r = TCPSocket.open(host, port){ |t|
  t.puts('NOOP')
  request << "NOOP\r\n"+t.gets
  t.puts("EHLO #{server}\r\n")
  request << "EHLO #{server}\r\n"+t.gets+t.gets+t.gets+t.gets+t.gets+t.gets+t.gets+t.gets+t.gets+t.gets
  t.puts("MAIL FROM:<#{from}>\r\n")
  request << "MAIL FROM:<#{from}>\r\n"+t.gets
  t.puts("RCPT TO:<#{to}>\r\n")
  request << "RCPT TO:<#{to}>\r\n"+t.gets
  t.puts("DATA\r\n")
  request << "DATA\r\n"+t.gets
  t.puts("#{data}\r\n.\r\n")
  request << "#{data}\r\n.\r\n"+t.gets
  t.puts("QUIT\r\n")
  request << "QUIT\r\n"+t.gets
}
puts request

Article by Maciej Walusiak Full Stack Developer @Railsware