Class: Cybertrain::Mailer

Inherits:
Object
  • Object
show all
Defined in:
cybertrain/mailer.rb

Overview

Sends plain-text e-mail through an SMTP server.

Examples:

config from the environment (CYBERTRAIN_SMTP_HOST, …)

require "cybertrain/mailer"
require "cybertrain/net/tls"

begin
  Cybertrain::Mailer.deliver(to: user.email, subject: "Welcome", body: "Hello!\n")
rescue Cybertrain::Mailer::Error => e
  Cybertrain.logger.warn("mail: #{e.message}") # never contains the password
end

explicit settings

mailer = Cybertrain::Mailer.new(host: "smtp.example.com", user: "app", password: ENV["SMTP_PASSWORD"].to_s,
                                from: "noreply@example.com")
mailer.deliver(to: "a@example.com, b@example.com", subject: "Report", body: text)

Defined Under Namespace

Classes: ConnectError, Error, InsecureError, ProtocolError, RejectedError, TLSError, TimeoutError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host: Cybertrain.config.smtp_host, port: Cybertrain.config.smtp_port, user: Cybertrain.config.smtp_user, password: Cybertrain.config.smtp_password, from: Cybertrain.config.smtp_from, tls: Cybertrain.config.smtp_tls, timeout: 10, deadline: 60, helo: "localhost") ⇒ Mailer

Each setting defaults to Config’s (smtp_host, smtp_port, …, from the CYBERTRAIN_SMTP_* environment variables).

Parameters:

  • host (String) (defaults to: Cybertrain.config.smtp_host) —

    the SMTP server’s name (or an IP address; TLS checks the certificate against this name, so give a name for TLS)

  • port (Integer) (defaults to: Cybertrain.config.smtp_port) —

    0 for the mode’s port (default_port)

  • user (String) (defaults to: Cybertrain.config.smtp_user) —

    the AUTH PLAIN user; “” sends no AUTH

  • password (String) (defaults to: Cybertrain.config.smtp_password) —

    never logged or shown

  • from (String) (defaults to: Cybertrain.config.smtp_from) —

    the sender address #deliver uses by default

  • tls (String) (defaults to: Cybertrain.config.smtp_tls) —

    “starttls” (the default), “tls” (implicit TLS) or “none” (plain text; refuses to send credentials except to loopback)

  • timeout (Numeric) (defaults to: 10) —

    seconds for each wait. Default 10.

  • deadline (Numeric) (defaults to: 60) —

    seconds for a whole delivery, name resolution included. Default 60.

  • helo (String) (defaults to: "localhost") —

    the name sent with EHLO. Default “localhost”.

Raises:

  • (ArgumentError) —

    an empty host, an unknown mode, a port out of range, a timeout that is not positive



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'cybertrain/mailer.rb', line 260

def initialize(host: Cybertrain.config.smtp_host, port: Cybertrain.config.smtp_port,
               user: Cybertrain.config.smtp_user, password: Cybertrain.config.smtp_password,
               from: Cybertrain.config.smtp_from, tls: Cybertrain.config.smtp_tls,
               timeout: 10, deadline: 60, helo: "localhost")
  @mail_host = host.to_s.downcase
  @mail_tls = tls.to_s.downcase
  @mail_user = user.to_s
  @password = password.to_s
  @mail_from = from.to_s
  @mail_timeout = timeout.to_f
  @mail_deadline = deadline.to_f
  @helo_name = helo.to_s
  raise ArgumentError, "no SMTP host: set CYBERTRAIN_SMTP_HOST (Config#smtp_host) or pass host:" if @mail_host.empty?
  raise ArgumentError, "the SMTP host name is malformed" unless Mailer.host_name?(@mail_host)
  unless @mail_tls == "starttls" || @mail_tls == "tls" || @mail_tls == "none"
    raise ArgumentError, "tls must be \"starttls\", \"tls\" or \"none\""
  end
  raise ArgumentError, "the EHLO name is malformed" unless Mailer.host_name?(@helo_name)

  configured_port = port.to_i
  raise ArgumentError, "the SMTP port must be from 1 to 65535 (0 for the mode's)" if configured_port < 0 || configured_port > 65_535

  @mail_port = configured_port == 0 ? Mailer.default_port(@mail_tls) : configured_port
  raise ArgumentError, "timeout must be positive" unless @mail_timeout > 0.0
  raise ArgumentError, "deadline must be positive" unless @mail_deadline > 0.0
end

Instance Attribute Details

#mail_deadline ⇒ Float (readonly)

Returns seconds a whole delivery may take.

Returns:

  • (Float) —

    seconds a whole delivery may take



229
230
231
# File 'cybertrain/mailer.rb', line 229

def mail_deadline
  @mail_deadline
end

#mail_from ⇒ String (readonly)

Returns the default sender address.

Returns:

  • (String) —

    the default sender address



220
221
222
# File 'cybertrain/mailer.rb', line 220

def mail_from
  @mail_from
end

#mail_host ⇒ String (readonly)

Returns the SMTP server.

Returns:

  • (String) —

    the SMTP server



211
212
213
# File 'cybertrain/mailer.rb', line 211

def mail_host
  @mail_host
end

#mail_port ⇒ Integer (readonly)

Returns its port.

Returns:

  • (Integer) —

    its port



214
215
216
# File 'cybertrain/mailer.rb', line 214

def mail_port
  @mail_port
end

#mail_timeout ⇒ Float (readonly)

Returns seconds any one wait may take.

Returns:

  • (Float) —

    seconds any one wait may take



226
227
228
# File 'cybertrain/mailer.rb', line 226

def mail_timeout
  @mail_timeout
end

#mail_tls ⇒ String (readonly)

Returns “starttls”, “tls” or “none”.

Returns:

  • (String) —

    “starttls”, “tls” or “none”



223
224
225
# File 'cybertrain/mailer.rb', line 223

def mail_tls
  @mail_tls
end

#mail_user ⇒ String (readonly)

Returns the AUTH user, “” for none.

Returns:

  • (String) —

    the AUTH user, “” for none



217
218
219
# File 'cybertrain/mailer.rb', line 217

def mail_user
  @mail_user
end

Class Method Details

.default_port(tls) ⇒ Integer

The port a mode uses when none is configured: 587 (submission) for starttls, 465 (submissions) for tls, 25 for none.

Parameters:

  • tls (String)

Returns:

  • (Integer)


236
237
238
239
240
241
# File 'cybertrain/mailer.rb', line 236

def self.default_port(tls)
  return 465 if tls == "tls"
  return 25 if tls == "none"

  587
end

.deliver(to:, subject:, body:, from: "") ⇒ String

Sends one message with the settings in Config (Mailer.new’s defaults).

Parameters:

  • to (String) —

    one address, or several separated by commas

  • subject (String) —

    UTF-8; non-ASCII is RFC 2047-encoded

  • body (String) —

    UTF-8 plain text, any line endings

  • from (String) (defaults to: "") —

    the sender; “” for Config#smtp_from

Returns:

  • (String) —

    the server’s reply to the message (“250 2.0.0 Ok: queued as …”)

Raises:

  • (Error) —

    a subclass; ArgumentError for an address, subject or body refused



297
298
299
# File 'cybertrain/mailer.rb', line 297

def self.deliver(to:, subject:, body:, from: "")
  Mailer.new.deliver(to: to, subject: subject, body: body, from: from)
end

Instance Method Details

#deliver(to:, subject:, body:, from: "") ⇒ String

Sends one message.

Parameters:

  • to (String) —

    one address, or several separated by commas

  • subject (String) —

    UTF-8; non-ASCII is RFC 2047-encoded

  • body (String) —

    UTF-8 plain text, any line endings

  • from (String) (defaults to: "") —

    the sender; “” for the mailer’s from:

Returns:

  • (String) —

    the server’s reply to the message

Raises:

  • (Error) —

    a subclass; ArgumentError for an address, subject or body refused



309
310
311
312
313
314
315
316
317
318
# File 'cybertrain/mailer.rb', line 309

def deliver(to:, subject:, body:, from: "")
  given = from.to_s
  sender = given.empty? ? @mail_from : given
  Mailer.check_address(sender, "from")
  recipients = Mailer.split_recipients(to.to_s)
  title = subject.to_s
  Mailer.check_subject(title)
  text = Mailer.normalize_body(body.to_s)
  perform(sender, recipients, title, text)
end