Як відправляти електронні листи за допомогою Ruby — розглядаємо на прикладах

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Привіт усім, мене звати Ігор. Я Full Stack розробник з досвідом в ІТ понад 11 років. У цій статті поділюся власним досвідом вирішення певного виду робочих задач, з якими я багато стикався у своїй практиці.

Уявімо, що у вас є робочий застосунок Ruby і вам потрібно додати до нього фічі розсилки імейлів. Це можуть бути імейли, які юзери отримують після реєстрації, або будь-які інші види транзакційних листів, це не має значення. Оскільки я дуже багато працював з такими задачами, я можу розказати, як це реалізувати різними способами та який з них мій улюблений.

Способи відправки імейлів в Ruby

Я знаю три основні способи реалізувати цей функціонал у Ruby-застосунках.

Якщо ви хочете взагалі уникнути залежностей — використовуйте вбудовану бібліотеку Net::SMTP. Вона реалізує розсилку через SMTP-сервер. Недолік цього способу полягає в тому, що Net::SMTP не дає можливості для створення імейлів. Ви завжди можете робити їх самостійно, але це потребує часу.

Інші два варіанти — це геми Ruby.

Другим і найпоширенішим способом є ActionMailer. Це гем, вбудований у фреймворк Rails, тому це найбільш звичний вибір для відправки імейлів з додатків, написаних на Rails.

Третій варіант — використовувати спеціалізовані геми Ruby, таки як Mail, Pony або інші. Ці рішення дозволяють просто й ефективно керувати email-активностями.

Тепер спробуємо надіслати електронний лист, використовуючи згадані рішення.

Відправка листів через Net::SMTP

Зі свого досвіду скажу, що мало хто використовує цей метод у звичайній вебпрограмі. Однак відправка імейлів через Net::SMTP може підійти вам, якщо ви пишете на mruby (полегшена реалізація мови Ruby) на деяких пристроях IoT.

Також спосіб підійде, якщо використовувати його в безсерверних обчисленнях, наприклад, AWS Lambda. Для початку, розглянемо приклад коду, а вже потім поринемо в деталі.

require 'net/smtp'

message = <<END_OF_MESSAGE
From: YourRubyApp <[email protected]>
To: User1 <[email protected]>
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,
  '[email protected]',
  '[email protected]'
end

Це простий приклад, як можна відправити текстовий лист через SMTP (офіційну документацію можна знайти тут). Ви можете побачити чотири заголовки: From, To, Subject та Date. Майте на увазі, що ви повинні відокремити їх порожнім рядком від основного тексту листа. Не менш важливим є підключення до SMTP-сервера.

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

Звичайно, тут треба підставити ваші дані замість ‘your.smtp.server‘ та 25 — номер порту за замовчуванням. За потреби ви можете вказати інші деталі, як-от ім’я користувача, пароль або схему автентифікації (:plain, :login, або :cram_md5). Ось так це може виглядати:

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

Тут ви підключитесь до SMTP-сервера, використовуючи ім’я користувача та пароль у форматі звичайного тексту, а ім’я хоста клієнта буде визначено як localhost.

Після цього можна використовувати метод send_message і вказати адреси відправника та одержувача як параметри. Блокова форма SMTP.start (Net::SMTP.start('your.smtp.server', 25) do |smtp|) автоматично закриває сеанс SMTP.

У Ruby Cookbook сендінг імейлів за допомогою бібліотеки Net::SMTP називається мінімалізмом, оскільки вам доведеться створювати тіло імейлів вручну. Проте, це не так безнадійно, як вам може здатися. Ви можете покращити свої імейли за допомогою HTML контенту та навіть додати вкладення.

Відправка HTML імейлів у Net::SMTP

Подивіться на приклад коду, що належить до розділу повідомлень.

message = <<END_OF_MESSAGE
From: YourRubyApp <[email protected]>
To: User1 <[email protected]>
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

Окрім тегів HTML у тілі повідомлення, ми маємо два додаткових заголовки: MIME-Version та Content-type. MIME (Multipurpose Internet Mail Extensions) — це розширення інтернет-протоколу електронної пошти, яке дозволяє поєднувати різні типи контенту в тілі повідомлення. Значення MIME-Version зазвичай становить 1.0. Це означає, що повідомлення має формат MIME.

Із заголовком Content-type все зрозуміло. У нашому випадку ми маємо два типи — HTML і звичайний текст. Також переконайтеся, що розділили ці типи контенту за допомогою визначення меж.

Окрім MIME-Version і Content-type, ви також можете використовувати такі заголовки MIME:

  • Content-Disposition — уточнює стиль презентації (inline або attachment);
  • Content-Transfer-Encoding — вказує на схему кодування у форматі binary-to-text (7 біт, друк в лапках, base64, 8 біт, або бінарний).

Ми розглянемо їх в одному з наступних прикладів.

Відправлення листів із вкладенням у Net::SMTP

Додамо в лист вкладення — PDF-файл. У цьому випадку нам потрібно змінити Content-type на multipart/mixed.Також, скористайтеся функцією pack("m"), щоб закодувати вкладення у base64.

part1 = <<END_OF_MESSAGE
From: YourRubyApp <[email protected]>
To: User1 <[email protected]>
Subject: Adding attachment to email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary = #{marker}
--#{marker}
END_OF_MESSAGE

Після цього вам треба визначити три частини вашого листа.

Частина 1. Головні заголовки.

part1 = <<END_OF_MESSAGE
From: YourRubyApp <[email protected]>
To: User1 <[email protected]>
Subject: Adding attachment to email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary = #{marker}
--#{marker}
END_OF_MESSAGE

Частина 2. Дія повідомлення.

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

Частина 3. Вкладення.

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

А тепер складаємо всі частини докупи та фіналізуємо скрипт. Ось як це виглядатиме:

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 <[email protected]>
To: User1 <[email protected]>
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,
    '[email protected]',
    '[email protected]'
  end

Чи можна відправляти імейли кільком одержувачам у Net::SMTP?

Так, безперечно можна: send_message очікує, що другий і наступні аргументи будуть містити електронні адреси одержувачів. Наприклад, так:

Net::SMTP.start('your.smtp.server', 25) do |smtp|
  smtp.send_message message,
  '[email protected]',
  '[email protected]',
  '[email protected]',
  '[email protected]'
end

Найкращі геми Ruby для відправки імейлів

В екосистемі Ruby ви можете знайти окремі геми для роботи з імейлами, які можуть суттєво покращити ваш досвід надсилання електронних листів. Ось ті, які я використовував сам і можу рекомендувати.

ActionMailer

Оскільки ActionMailer вбудований у Rails, не дивно, що він є найпоширенішим інструментом для відправлення імейлів. Якщо ваш застосунок написаний на Rails, ActionMailer вам обов’язково знадобиться. Гем дозволяє відправляти листи за допомогою класів та звичайних вьюх. Наведу приклад простого імейла на базі ActionMailer.

class TestMailer < ActionMailer::Base
  default from: '[email protected]'
  def simple_message(recipient)
    mail(
      to: recipient,
      subject: 'Any subject you want',
      body: 'Lorem Ipsum'
    )
  end
end

Як відправити імейл за допомогою ActionMailer

У RailsGuides ви можете знайти повну інформацію про те, як найкраще використовувати ActionMailer. Нам же потрібно створити та відправити імейл. Ось покрокова інструкція як це зробити.

Створіть модель мейлера

Ваша програма відправлятиме електронні листи за допомогою моделі та вью мейлера. Тож, спочатку нам потрібно їх створити.

$ rails generate mailer Notifier

Для того, щоб створити повідомлення імейлу нам необхідна допомога помічників, що визначені у моделі мейлера. Ви можете налаштувати змінні у вью мейлера, опції в листі такі як :to address, а також вкладення. Крім того, різні помічники доступні для того, щоб:

додати вкладення:

attachments['filename.png'] = File.read('path/to/attachment.pdf')

додати вкладення:

attachments.inline['filename.png'] = File.read('path/to/attachment.pdf')

вказати поле заголовка:

headers['X-No-Spam'] = 'True'

вказати декілька заголовків:

headers({'X-No-Spam' => 'True', 'In-Reply-To' => '[email protected]'})

вказати імейл для відправки:

mail

Ось так все має виглядати:

class UserMailer< ApplicationMailer
  def simple_message(recipient)
    attachments["attachment.pdf"] = File.read("path/to/file.pdf")
    mail(
      to: "[email protected]",
      subject: "New account information"
    )
  end

Цей клас мейлера вже містить вкладення та HTML-контент. Тепер нам треба створити відповідне вью.

Створіть вью

Вью визначає шаблон для використання з мейлером. Нам потрібно створити файл .erb з такою самою назвою, як і метод у класі мейлера. У моєму випадку це simple_message.html.erb. Знаходиться він в app/views/notifier_mailer/

Цей шаблон буде використовуватися для імейлів у форматі HTML. Для того, щоб додати текстову частину цього листа, треба створити simple_message.txt.erb у тій самій директорії. Тепер заповніть обидва файли відповідним контентом.

Конфігурації сервера

Наступний крок — налаштування ActionMailer і визначення методу доставки. SMTP встановлено за замовчуванням і ви можете налаштувати його і за допомогою config.action_mailer.smtp_settings.

Ви можете вибрати інший метод доставки, такий як :sendmail, `:file` (зберегти імейли у файлах) і `:test` (зберегти імейли в масиві ActionMailer::Base.deliveries). Ось приклад конфігурації SMTP:

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
}

Порт 25 встановлено за замовчуванням, і ви можете вибрати plain, login чи cram_md5 у якості варіантів автентифікації.

Відправка листів

Ми визначили мейлер та шаблон і готові доставити повідомлення. Для цього просто викликаємо deliver_now. За бажанням можна відкласти доставку на потім за допомогою методу deliver_later. В ActionMailer вам потрібно викликати UserMailer.simple_message('<a href="mailto:[email protected]">[email protected]</a>').deliver_now.

Відправка імейлів кільком адресатам

Щоб налаштувати відправку імейлів кільком одержувачам, необхідно встановити список адрес електронної пошти на ключ :to, так само як і на ключі :cc та :bcc. Ви можете створити список у вигляді масиву адрес одержувачів або одного рядка, у якому адреси вказуються через кому.

class AdminMailer < ApplicationMailer
  default to: -> { Admin.pluck(:email) },
  from: '[email protected]'
  def new_registration(user)
    @user = user
    mail(subject: "New User Signup: #{@user.email}")
  end
end

Якщо ви хочете дізнатися більше про Action Mailer, я можу вам порекомендувати вичерпну інструкцію Метью Кроука.

Ruby Mail

Ця бібліотека дає вам можливість керувати всіма діями, пов’язаними з імейлами, включно з відправкою та отриманням листів в одному місці. Щоб почати роботу з Mail, виконайте команду require ‘mail’.

mail = Mail.new do
  from    '[email protected]'
  to      '[email protected]'
  subject 'Any subject you want'
  body    'Lorem Ipsum'
end

SMTP — це дефолтний метод доставки пошти з портом локального хосту 25. Ви можете змінити налаштування SMTP:

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

або навіть спосіб доставки:

mail.delivery_method :sendmail
mail.deliver

Відправка листа з HTML і вкладенням

З Ruby Mail у вас не виникне жодних проблем зі створенням багатокомпонентного листа. Наприклад:

mail = Mail.new do
  from    '[email protected]'
  to      '[email protected]'
  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

Це основне, що треба знати про роботу Ruby Mail. Більше деталей ви знайдете в описі бібліотеки.

Pony

Можливо, ви чули казочки про те, що існує спосіб відправки імейлів за допомогою лише однієї команди. Втім такий спосіб дійсно можливо реалізувати за допомогою гема Pony. Ось так:

Pony.mail(:to => '[email protected]omain.com', :from => '[email protected]', :subject => 'Any subject you want', :body => 'Lorem Ipsum')

Насправді ви можете скоротити цей сценарій та мати лише ключ параметра :to, якщо це необхідно. Також можна вказати спосіб доставки:

Pony.mail(:to => '[email protected]', :via => :smtp)

або

Pony.mail(:to => '[email protected]', :via => :sendmail)

І ось так ви можете налаштувати свій SMTP-сервер у Pony:

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

Варіанти авторизації включають plain, login, та cram_md5. За замовчуванням авторизація не встановлена.

Як відправляти імейли через Gmail SMTP

Насправді цю частину можна повністю пропустити, оскільки вона не має практичного застосування. Ви можете використати будь-який інструмент, щоб відправляти пошту через Gmail. Ось приклад для 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

Ось конфігурація Gmail SMTP-сервера для 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
}

А ось це для Pony:

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

Mailtrap для відправки листів з Ruby

Якщо вам потрібен надійний та простий інструмент для відправки імейлів у Ruby-застосунках, рекомендую розглянути Mailtrap Email API. Це Email API/SMTP Relay, який зберігає журнал імейлів до 60 днів, щоб ви легко змогли знайти причину проблеми та вирішувати її, якщо така з’явиться.

Зі зручностей тут є зрозумілі борди з даними про стан імейл інфраструктури для кращої аналітики, а також своєчасні сповіщення про доставку імейлів. Тобто, якщо раптом щось піде не так з доставкою листів, ви отримаєте алерт і підказку про те, де шукати причину проблеми. А бонусом вам надсилатимуть щотижневі звіти про ефективність доставки листів.

Для користувачів ActionMailer та/або Rails є спеціальний метод доставки, який дозволяє їм використовувати API із наявною функціональністю ActionMailer і простим налаштуванням. Все що необхідно зробити — це встановити ключ API та обрати метод доставки. Після цього можна продовжувати використовувати ActionMailer, як і раніше.

Але повернімося до нашого завдання. Щоб налаштувати функцію відправки імейлів через Mailtrap, необхідно додати наступну строку до Gemfile застосунку:

gem 'mailtrap'

а потім виконати

$ bundle install

або ж самостійно встановити наступним чином:

$ gem install mailtrap

Для ActionMailer

Цей гем додає власний спосіб доставки в ActionMailer. Для налаштування, додайте наступне до конфігурації ActionMailer (у Rails проєктах розташований в 

config/$ENVIRONMENT.rb
):
config.action_mailer.delivery_method = :mailtrap
config.action_mailer.mailtrap_settings = {
  api_key: ENV.fetch('MAILTRAP_API_KEY')
}

Це все. Можна користуватись ActionMailer, як і раніше.

Щоб додати category та custom_variables, додайте їх під час генерації листа:

mail(
  to: '[email protected]',
  subject: 'You are awesome!',
  category: 'Test category',
  custom_variables: { test_variable: 'abc' }
)

Тепер, якщо у вас немає Rails, або ви не використовуєте ActionMailer, візьміть гем Mail (також для опції Net::SMTP) для інтеграції.

Більше інформації про те, як повноцінно використовувати пакет Ruby для відправки імейлів із Mailtrap можете знайти в цій документації.

Конфігурація сервера

Ось як конфігурація SMTP-сервера для початку відправки імейлів виглядатиме в Mailtrap (перейдіть до облікових даних SMTP в Mailtrap інтерфейсі, просто скопіюйте та вставте їх):

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address   => 'send.smtp.mailtrap.io',
  :port      => 587,
  :domain    => 'yourdomain',
  :user_name => 'api',
  :password  => '********059a',
  :authentication => 'plain',
  :enable_starttls_auto => true 
}

Інтеграція API для гему Mailtrap Ruby

Все дуже просто. Використайте ось цей код для Mailtrap API relay.

require "mailtrap"

mail = Mailtrap::Sending::Mail.new(
  from:
    {
      email: "[email protected]",
      name: "Mailtrap Test",
    },
  to: [
    {
      email: "[email protected]",
    }
  ],
  subject: "You are awesome!",
  text: "Congrats for sending test email with Mailtrap!",
  category: "Integration Test",

client = Mailtrap::Sending::Client.new(
  api_key: "f3368c69855590e3095f329b0ee5059a",
  api_host: "https://send.api.mailtrap.io/",
)

response = client.send(mail)
puts response

Звісно, ви можете змінювати значення «from:», «to:», «subject:», «headers:» і «content:».

По суті це все, що треба знати про відправку листів з Ruby з та без Mailtrap Email API.

До речі, якщо ваш застосунок використовує Net::SMTP або ActionMailer, Mailtrap має додатковий функціонал для тестування сендінгу.

Фіналочка

Не важливо, який спосіб відправки імейлів ви оберете, головне — завжди все протестувати спочатку. Мені подобається Mailtrap саме через фічі для тестування. А ви якій спосіб відправки листів використовуєте та чому?

👍ПодобаєтьсяСподобалось7
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Підписатись на коментарі