Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/generators/supermail/email/templates/email.rb.tt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ class <%= class_name %>Email < ApplicationEmail
def body = <<~PLAIN

PLAIN

def html_body = <<~HTML

HTML
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ class ApplicationEmail < Supermail::Rails::Base
The Example.com Team
_
end

def html_body
<<~HTML
#{yield if block_given?}
<p>Best,</p>
<p>The Example.com Team</p>
HTML
end
end
41 changes: 38 additions & 3 deletions lib/supermail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,57 @@ class Base
:deliver,
:deliver_now,
:deliver_later,
:message,
to: :action_mailer_base_mail
to: :message

def message
@message ||= action_mailer_base_mail
end

def to = nil
def from = nil
def subject = nil
def cc = []
def bcc = []
def body = ""
def html_body = nil

# Generate a mailto: URL with appropriate escaping.
def mailto = MailTo.href(to:, from:, cc:, bcc:, subject:, body:)
alias :mail_to :mailto

private def action_mailer_base_mail
ActionMailer::Base.mail(to:, from:, cc:, bcc:, subject:, body:)
if html_body && !html_body.empty?
# Multipart email with both text and HTML
# Capture values to avoid context issues in blocks
email_to = to
email_from = from
email_cc = cc
email_bcc = bcc
email_subject = subject
text_body = body
html_content = html_body

Mail.new do
to email_to
from email_from
cc email_cc if email_cc && !email_cc.empty?
bcc email_bcc if email_bcc && !email_bcc.empty?
subject email_subject

text_part do
body text_body
end

html_part do
content_type 'text/html; charset=UTF-8'
body html_content
end
end
else
# Plain text only
# ActionMailer::Base.mail returns a MessageDelivery, extract the message
ActionMailer::Base.mail(to:, from:, cc:, bcc:, subject:, body:).message
end
end
end
end
Expand Down
175 changes: 175 additions & 0 deletions spec/supermail_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,178 @@ def initialize(to:, from:, subject:, body:, cc: [], bcc: [])
end
end
end

class HtmlMailer < Supermail::Rails::Base
def initialize(to:, from:, subject:, body:, html_body:)
@to = to
@from = from
@subject = subject
@body = body
@html_body = html_body
end

attr_reader :to, :from, :subject, :body, :html_body
end

RSpec.describe HtmlMailer do
let(:email) { described_class.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "Hi there",
html_body: "<p>Hi there</p>")
}
let(:message) { email.message }

it "creates a multipart email" do
expect(message.multipart?).to be true
end

it "includes text part" do
text_part = message.text_part
expect(text_part).not_to be_nil
expect(text_part.body.to_s).to eq("Hi there")
end

it "includes html part" do
html_part = message.html_part
expect(html_part).not_to be_nil
expect(html_part.body.to_s).to eq("<p>Hi there</p>")
end

context "when html_body is nil" do
let(:email) { described_class.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "Hi there",
html_body: nil)
}

it "creates a plain text email" do
expect(message.multipart?).to be false
expect(message.body.to_s).to eq("Hi there")
end
end

context "when html_body is empty" do
let(:email) { described_class.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "Hi there",
html_body: "")
}

it "creates a plain text email" do
expect(message.multipart?).to be false
expect(message.body.to_s).to eq("Hi there")
end
end

context "when html_body contains only whitespace" do
let(:email) { described_class.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "Hi there",
html_body: " \n\t ")
}

it "creates a multipart email" do
# Mail gem may strip whitespace-only content, which is expected behavior
expect(message.multipart?).to be true
end
end

context "with cc and bcc" do
class HtmlMailerWithCc < Supermail::Rails::Base
def initialize(to:, from:, subject:, body:, html_body:, cc:, bcc:)
@to = to
@from = from
@subject = subject
@body = body
@html_body = html_body
@cc = cc
@bcc = bcc
end

attr_reader :to, :from, :subject, :body, :html_body, :cc, :bcc
end

let(:email) { HtmlMailerWithCc.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "Hi there",
html_body: "<p>Hi there</p>",
cc: ["cc@example.com"],
bcc: ["bcc@example.com"])
}
let(:message) { email.message }

it "includes cc recipients" do
expect(message.cc).to eq(["cc@example.com"])
end

it "includes bcc recipients" do
expect(message.bcc).to eq(["bcc@example.com"])
end

it "creates a multipart email" do
expect(message.multipart?).to be true
end
end

context "html part content type" do
it "sets correct content type" do
html_part = message.html_part
expect(html_part.content_type).to include("text/html")
expect(html_part.content_type).to include("charset=UTF-8")
end
end

context "delivery methods" do
let(:email) { described_class.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "Hi there",
html_body: "<p>Hi there</p>")
}

it "responds to deliver" do
expect(email).to respond_to(:deliver)
end

it "responds to deliver_now" do
expect(email).to respond_to(:deliver_now)
end

it "responds to deliver_later" do
expect(email).to respond_to(:deliver_later)
end
end

context "when body is empty but html_body is present" do
let(:email) { described_class.new(
to: "user@example.com",
from: "support@example.com",
subject: "Hello",
body: "",
html_body: "<p>Hi there</p>")
}

it "creates a multipart email" do
expect(message.multipart?).to be true
end

it "includes empty text part" do
expect(message.text_part.body.to_s).to eq("")
end

it "includes html part" do
expect(message.html_part.body.to_s).to eq("<p>Hi there</p>")
end
end
end