본문으로 건너뛰기
김신건의 로그

[Rails] Action Mailer: 이메일 발송

· 수정 · 📖 약 1분 · 478자/단어 #rails #action-mailer #email #smtp
action mailer, rails mailer, deliver_later, MailerPreview, smtp_settings

정의

Action Mailer는 Rails의 이메일 처리 프레임워크. 컨트롤러처럼 구조화된 mailer 클래스 + ERB 템플릿 + Active Job 통합. SMTP, Sendmail, SES, SendGrid 등 다양한 백엔드.

생성

bin/rails generate mailer UserMailer welcome reset_password

생성:

  • app/mailers/user_mailer.rb
  • app/mailers/application_mailer.rb
  • app/views/user_mailer/welcome.html.erb, .text.erb
  • app/views/layouts/mailer.html.erb, .text.erb
  • test/mailers/user_mailer_test.rb

Mailer 클래스

# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"
  layout "mailer"
end

# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def welcome(user)
    @user = user
    @url = root_url
    mail(to: user.email, subject: "환영합니다")
  end

  def reset_password(user, token)
    @user = user
    @reset_url = edit_password_reset_url(token)
    mail(
      to: user.email,
      subject: "비밀번호 재설정",
      cc: ["audit@example.com"],
      reply_to: "support@example.com",
    )
  end
end

템플릿

<%# app/views/user_mailer/welcome.html.erb %>
<!DOCTYPE html>
<html>
<body>
  <h1><%= @user.name %>님 환영합니다!</h1>
  <p>가입을 완료하려면 <%= link_to "여기", @url %>를 클릭하세요.</p>
</body>
</html>
<%# app/views/user_mailer/welcome.text.erb %>
<%= @user.name %>님 환영합니다!

가입 완료: <%= @url %>

HTML + text 둘 다 자동으로 multipart 메일에 포함. 클라이언트가 선택.

전송

# 즉시
UserMailer.welcome(user).deliver_now

# 비동기 (Active Job)
UserMailer.welcome(user).deliver_later

# 옵션
UserMailer.welcome(user).deliver_later(
  wait: 5.minutes,
  wait_until: Date.tomorrow.noon,
  queue: "mailers",
  priority: 10,
)

기본 큐 이름: mailers. Sidekiq/Solid Queue 워커가 처리.

설정

# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address: ENV["SMTP_HOST"],
  port: 587,
  domain: "example.com",
  user_name: ENV["SMTP_USER"],
  password: ENV["SMTP_PASS"],
  authentication: "plain",
  enable_starttls_auto: true,
}
config.action_mailer.default_url_options = { host: "example.com" }
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = true

다른 백엔드

Backend용도
:smtp표준 SMTP
:sendmail시스템 sendmail
:file파일에 저장 (개발)
:test메모리 (ActionMailer::Base.deliveries)
:async_job
# 개발
config.action_mailer.delivery_method = :letter_opener    # gem
# 브라우저에서 자동 열림

URL helpers in mailer

Mailer는 별도 컨텍스트라 host 명시 필요.

# config/environments/production.rb
config.action_mailer.default_url_options = { host: "example.com", protocol: "https" }

# config/environments/development.rb
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
<%= link_to "프로필", profile_url(@user) %>    <%# url 헬퍼 (path 아님) %>

*_url 사용 (*_path는 mailer에서 사용 X).

첨부

def invoice(invoice)
  @invoice = invoice
  attachments["invoice.pdf"] = File.read(invoice.pdf_path)
  # 또는 ActiveStorage
  attachments["invoice.pdf"] = invoice.pdf_blob.download
  mail(to: invoice.user.email, subject: "청구서")
end

inline 이미지:

attachments.inline["logo.png"] = File.read("app/assets/images/logo.png")
<%= image_tag attachments["logo.png"].url %>

Preview

bin/rails generate mailer가 자동 생성.

# test/mailers/previews/user_mailer_preview.rb
class UserMailerPreview < ActionMailer::Preview
  def welcome
    user = User.first
    UserMailer.welcome(user)
  end

  def reset_password
    user = User.first
    UserMailer.reset_password(user, "test-token")
  end
end

브라우저: http://localhost:3000/rails/mailers

각 메일을 HTML/text로 미리 보기. 디자인 작업에 유용.

테스트

class UserMailerTest < ActionMailer::TestCase
  test "welcome" do
    user = users(:alice)
    mail = UserMailer.welcome(user)

    assert_equal "환영합니다", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name, mail.body.encoded
  end
end

RSpec:

RSpec.describe UserMailer, type: :mailer do
  let(:user) { create(:user) }
  let(:mail) { UserMailer.welcome(user) }

  it "renders" do
    expect(mail.subject).to eq("환영합니다")
    expect(mail.to).to eq([user.email])
    expect(mail.body.encoded).to match(user.name)
  end
end

전송 카운트:

assert_emails 1 do
  UserMailer.welcome(user).deliver_now
end

Action Mailbox (수신)

이메일 수신 처리.

class InboundMailbox < ApplicationMailbox
  def process
    parent = User.find_by(email: mail.from)
    return unless parent

    Comment.create!(author: parent, body: mail.body.decoded)
  end
end

라우팅:

# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
  routing /^reply-/i => :inbound
  routing "support@example.com" => :support
end

수신 게이트웨이: SendGrid Inbound, Mailgun, Postfix 등.

자주 보는 패턴

사용자 알림

class NotificationMailer < ApplicationMailer
  def comment_added(comment)
    @comment = comment
    @post = comment.post
    mail(to: @post.author.email, subject: "새 댓글")
  end
end

# Comment 모델
after_create_commit -> { NotificationMailer.comment_added(self).deliver_later }

다국어

def welcome(user)
  I18n.with_locale(user.locale) do
    mail(to: user.email, subject: I18n.t("mailer.welcome.subject"))
  end
end
<h1><%= t("mailer.welcome.title", name: @user.name) %></h1>

발송 실패 처리

class UserMailer < ApplicationMailer
  rescue_from Net::SMTPAuthenticationError do |e|
    Rails.logger.error("SMTP auth failed: #{e.message}")
    SentryService.notify(e)
  end
end

대량 발송 (rate limit)

users.find_each do |user|
  UserMailer.newsletter(user).deliver_later(wait: rand(0..3600).seconds)
end

랜덤 분산으로 SMTP rate limit 회피.

함정

1. deliver_now in controller

def signup
  user.save
  UserMailer.welcome(user).deliver_now    # 1-3초 응답 지연
  redirect_to root_path
end

deliver_later.

2. 트랜잭션과 deliver_later

User.transaction do
  user = User.create!(...)
  UserMailer.welcome(user).deliver_later
end
# 트랜잭션 rollback 시 메일은 이미 enqueue → 워커가 user 못 찾음

해결: Solid Queue (같은 DB라 안전) 또는 after_commit.

3. 큰 첨부

이미지 5MB+ → SMTP 거부. 큰 파일은 다운로드 URL로.

4. SPF/DKIM 미설정

from: noreply@example.com 보내려면 도메인에 SPF/DKIM. 안 그러면 스팸. SES/SendGrid 같은 서비스는 자체 도메인 사용.

5. 개발에서 실제 발송

# config/environments/development.rb
config.action_mailer.delivery_method = :test    # 또는 :letter_opener

:smtp로 두면 개발 중 실제 발송 위험.

외부 서비스 통합

서비스gem
SendGridsendgrid-ruby 또는 SMTP
Mailgunmailgun-ruby 또는 SMTP
AWS SESaws-sdk-rails
Postmarkpostmark-rails

SMTP가 가장 간단하나 API 사용이 더 강력 (tags, metadata, webhook).

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기