diff --git a/lib/generators/supermail/email/templates/email.rb.tt b/lib/generators/supermail/email/templates/email.rb.tt index 3c16d13..634ec11 100644 --- a/lib/generators/supermail/email/templates/email.rb.tt +++ b/lib/generators/supermail/email/templates/email.rb.tt @@ -4,4 +4,8 @@ class <%= class_name %>Email < ApplicationEmail def body = <<~PLAIN PLAIN + + def html_body = <<~HTML + + HTML end diff --git a/lib/generators/supermail/install/templates/application_email.rb.tt b/lib/generators/supermail/install/templates/application_email.rb.tt index 5b67e72..73a7eb9 100644 --- a/lib/generators/supermail/install/templates/application_email.rb.tt +++ b/lib/generators/supermail/install/templates/application_email.rb.tt @@ -10,4 +10,12 @@ class ApplicationEmail < Supermail::Rails::Base The Example.com Team _ end + + def html_body + <<~HTML + #{yield if block_given?} +

Best,

+

The Example.com Team

+ HTML + end end diff --git a/lib/supermail.rb b/lib/supermail.rb index 6bcdb88..6ffb67e 100644 --- a/lib/supermail.rb +++ b/lib/supermail.rb @@ -13,8 +13,11 @@ 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 @@ -22,13 +25,45 @@ 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 diff --git a/spec/supermail_spec.rb b/spec/supermail_spec.rb index bbf79ec..c85feb4 100644 --- a/spec/supermail_spec.rb +++ b/spec/supermail_spec.rb @@ -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: "

Hi there

") + } + 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("

Hi there

") + 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: "

Hi there

", + 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: "

Hi there

") + } + + 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: "

Hi there

") + } + + 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("

Hi there

") + end + end +end