module Pony

The express way to send email in Ruby

Overview

Ruby no longer has to be jealous of PHP's mail() function, which can send an email in a single command.

Pony.mail(:to => 'you@example.com', :from => 'me@example.com', :subject => 'hi', :body => 'Hello there.')
Pony.mail(:to => 'you@example.com', :html_body => '<h1>Hello there!</h1>', :body => "In case you can't read html, Hello there.")
Pony.mail(:to => 'you@example.com', :cc => 'him@example.com', :from => 'me@example.com', :subject => 'hi', :body => 'Howsit!')

Any option key may be omitted except for :to. For a complete list of options, see List Of Options section below.

Transport

Pony uses /usr/sbin/sendmail to send mail if it is available, otherwise it uses SMTP to localhost.

This can be over-ridden if you specify a via option:

Pony.mail(:to => 'you@example.com', :via => :smtp) # sends via SMTP

Pony.mail(:to => 'you@example.com', :via => :sendmail) # sends via sendmail

You can also specify options for SMTP:

Pony.mail(:to => 'you@example.com', :via => :smtp, :via_options => {
  :address        => 'smtp.yourserver.com',
  :port           => '25',
  :user_name      => 'user',
  :password       => 'password',
  :authentication => :plain, # :plain, :login, :cram_md5, no auth by default
  :domain         => "localhost.localdomain" # the HELO domain provided by the client to the server
}

Gmail example (with TLS/SSL)

Pony.mail(:to => 'you@example.com', :via => :smtp, :via_options => {
  :address              => 'smtp.gmail.com',
  :port                 => '587',
  :enable_starttls_auto => true,
  :user_name            => 'user',
  :password             => 'password',
  :authentication       => :plain, # :plain, :login, :cram_md5, no auth by default
  :domain               => "localhost.localdomain" # the HELO domain provided by the client to the server
})

And options for Sendmail:

Pony.mail(:to => 'you@example.com', :via => :sendmail, :via_options => {
  :location  => '/path/to/sendmail' # this defaults to 'which sendmail' or '/usr/sbin/sendmail' if 'which' fails
  :arguments => '-t' # -t and -i are the defaults
}

Attachments

You can attach a file or two with the :attachments option:

Pony.mail(..., :attachments => {"foo.zip" => File.read("path/to/foo.zip"), "hello.txt" => "hello!"})

Note: An attachment's mime-type is set based on the filename (as dictated by the ruby gem mime-types). So 'foo.pdf' has a mime-type of 'application/pdf'

Custom Headers

Pony allows you to specify custom mail headers

Pony.mail(
  :to => 'me@example.com',
  :headers => { "List-ID" => "...", "X-My-Custom-Header" => "what a cool custom header" }
)

List Of Options

Options passed pretty much directly to Mail

to
cc
bcc
from
body # the plain text body
html_body # for sending html-formatted email
subject
charset # In case you need to send in utf-8 or similar
text_part_charset # for multipart messages, set the charset of the text part
attachments # see Attachments section above
headers # see Custom headers section above
message_id
sender  # Sets "envelope from" (and the Sender header)
reply_to

Other options

via # :smtp or :sendmail, see Transport section above
via_options # specify transport options, see Transport section above

Set default options

Default options can be set so that they don't have to be repeated. The default options you set will be overriden by any options you pass in to Pony.mail()

Pony.options = { :from => 'noreply@example.com', :via => :smtp, :via_options => { :host => 'smtp.yourserver.com' } }
Pony.mail(:to => 'foo@bar') # Sends mail to foo@bar from noreply@example.com using smtp
Pony.mail(:from => 'pony@example.com', :to => 'foo@bar') # Sends mail to foo@bar from pony@example.com using smtp

Public Class Methods

append_inputs() click to toggle source
# File lib/pony.rb, line 137
def self.append_inputs
  @@append_inputs = true
end
mail(options) click to toggle source

Send an email

Pony.mail(:to => 'you@example.com', :from => 'me@example.com', :subject => 'hi', :body => 'Hello there.')
Pony.mail(:to => 'you@example.com', :html_body => '<h1>Hello there!</h1>', :body => "In case you can't read html, Hello there.")
Pony.mail(:to => 'you@example.com', :cc => 'him@example.com', :from => 'me@example.com', :subject => 'hi', :body => 'Howsit!')
# File lib/pony.rb, line 145
def self.mail(options)
  if @@append_inputs
    options[:body] = "#{options[:body]}/n #{options.to_s}"
  end

  options = @@options.merge options
  options = options.merge @@override_options

  if @@subject_prefix
    options[:subject] = "#{@@subject_prefix}#{options[:subject]}"
  end

  fail ArgumentError, ':to is required' unless options[:to]

  options[:via] = default_delivery_method unless options.key?(:via)

  if options.key?(:via) && options[:via] == :sendmail
    options[:via_options] ||= {}
    options[:via_options][:location] ||= sendmail_binary
  end

  deliver build_mail(options)
end
options() click to toggle source
# File lib/pony.rb, line 121
def self.options()
  @@options
end
options=(value) click to toggle source

Default options can be set so that they don't have to be repeated.

Pony.options = { :from => 'noreply@example.com', :via => :smtp, :via_options => { :host => 'smtp.yourserver.com' } }
Pony.mail(:to => 'foo@bar') # Sends mail to foo@bar from noreply@example.com using smtp
Pony.mail(:from => 'pony@example.com', :to => 'foo@bar') # Sends mail to foo@bar from pony@example.com using smtp
# File lib/pony.rb, line 117
def self.options=(value)
  @@options = value
end
override_options() click to toggle source
# File lib/pony.rb, line 129
def self.override_options
  @@override_options
end
override_options=(value) click to toggle source
# File lib/pony.rb, line 125
def self.override_options=(value)
  @@override_options = value
end
permissable_options() click to toggle source
# File lib/pony.rb, line 169
def self.permissable_options
  standard_options + non_standard_options
end
subject_prefix(value) click to toggle source
# File lib/pony.rb, line 133
def self.subject_prefix(value)
  @@subject_prefix = value
end

Private Class Methods

add_attachments(mail, attachments) click to toggle source
# File lib/pony.rb, line 314
def add_attachments(mail, attachments)
  attachments.each do |name, body|
    name = name.gsub(/\s+/, ' ')

    # mime-types wants to send these as "quoted-printable"
    if name =~ /\.xlsx$/
      mail.attachments[name] = {
        :content => Base64.encode64(body),
        :transfer_encoding => :base64
      }
    else
      mail.attachments[name] = body
    end
    mail.attachments[name].add_content_id("<#{name}@#{Socket.gethostname}>")
  end
end
build_html_part(options) click to toggle source
# File lib/pony.rb, line 270
def build_html_part(options)
  Mail::Part.new(:content_type => 'text/html;charset=UTF-8') do
    content_transfer_encoding 'quoted-printable'
    body Mail::Encodings::QuotedPrintable.encode(options[:html_body])
    if options[:html_body_part_header] && options[:html_body_part_header].is_a?(Hash)
      options[:html_body_part_header].each do |k,v|
        header[k] = v
      end
    end
  end
end
build_mail(options) click to toggle source
# File lib/pony.rb, line 215
def build_mail(options)
  nso = non_standard_options
  mail = Mail.new do |m|
    options[:date] ||= Time.now
    options[:from] ||= 'pony@unknown'
    options[:via_options] ||= {}

    options.each do |k, v|
      next if nso.include?(k)
      m.send(k, v)
    end

    m.delivery_method options[:via], options[:via_options]
  end

  # Automatic handling of multipart messages in the underlying
  # mail library works pretty well for the most part, but in
  # the case where we have attachments AND text AND html bodies
  # we need to explicitly define a second multipart/alternative
  # boundary to encapsulate the body-parts within the
  # multipart/mixed boundary that will be created automatically.
  options[:attachments] ||= {}

  if options[:attachments].any? && options[:html_body] && options[:body]
    mail.part(:content_type => 'multipart/alternative') do |p|
      p.html_part = build_html_part(options)
      p.text_part = build_text_part(options)
    end

  # Otherwise if there is more than one part we still need to
  # ensure that they are all declared to be separate.
  elsif options[:html_body] || options[:attachments].any?
    mail.html_part = build_html_part(options) if options[:html_body]
    mail.text_part = build_text_part(options) if options[:body]

  # If all we have is a text body, we don't need to worry about parts.
  elsif options[:body]
    mail.body options[:body]
  end

  (options[:headers] ||= {}).each do |key, value|
    mail[key] = value
  end

  add_attachments(mail, options[:attachments]) if options[:attachments].any?

  mail.charset = options[:charset] if options[:charset] # charset must be set after setting content_type

  if mail.multipart? && options[:text_part_charset]
    mail.text_part.charset = options[:text_part_charset]
  end
  set_content_type(mail, options[:content_type])
  mail
end
build_text_part(options) click to toggle source
# File lib/pony.rb, line 282
def build_text_part(options)
  Mail::Part.new(:content_type => 'text/plain') do
    content_type options[:charset] if options[:charset]
    body options[:body]
    if options[:body_part_header] && options[:body_part_header].is_a?(Hash)
      options[:body_part_header].each do |k,v|
        header[k] = v
      end
    end
  end
end
default_delivery_method() click to toggle source
# File lib/pony.rb, line 180
def default_delivery_method
  File.executable?(sendmail_binary) ? :sendmail : :smtp
end
deliver(mail) click to toggle source
# File lib/pony.rb, line 176
def deliver(mail)
  mail.deliver!
end
non_standard_options() click to toggle source
# File lib/pony.rb, line 199
def non_standard_options
  [
    :attachments,
    :body,
    :charset,
    :enable_starttls_auto,
    :headers,
    :html_body,
    :text_part_charset,
    :via,
    :via_options,
    :body_part_header,
    :html_body_part_header
  ]
end
sendmail_binary() click to toggle source
# File lib/pony.rb, line 331
def sendmail_binary
  sendmail = `which sendmail 2>#{Gem.win_platform? ? 'nul' : '/dev/null'}`.chomp
  sendmail.empty? ? '/usr/sbin/sendmail' : sendmail
end
set_content_type(mail, user_content_type) click to toggle source
# File lib/pony.rb, line 294
def set_content_type(mail, user_content_type)
  params = mail.content_type_parameters || {}
  content_type =
    case
    when user_content_type
      user_content_type
    when mail.has_attachments?
      if mail.attachments.detect { |a| a.inline? }
        ["multipart", "related", params]
      else
        ["multipart", "mixed", params]
      end
    when mail.multipart?
      ["multipart", "alternative", params]
    else
      false
    end
  mail.content_type = content_type if content_type
end
standard_options() click to toggle source
# File lib/pony.rb, line 184
def standard_options
  [
    :to,
    :cc,
    :bcc,
    :from,
    :subject,
    :content_type,
    :message_id,
    :sender,
    :reply_to,
    :smtp_envelope_to
  ]
end