Rails 7 is trying to use .deliver instead of deliver_now

165 Views Asked by At

I have this class to send an mail, using rails 7

class Mailer < ApplicationMailer
  def initialize(emails)
    super()
    @emails = emails
  end 

  def send()
    mail(
      to: @emails,
      from: '[email protected]' ,
      subject: 'example'
    )
  end
end

I want to inject at initialize an Mailer class

class MailService 
  
    def initialize(sender:)
      @sender= sender
    end    

    def submit_mail
      @sender.send().deliver_now
      # @sender.send().deliver //work but i need use deliver_now 
    end 
  end

i am using like this:

mailer = Mailer.new('[email protected]')
MailService.submit_mail(sender: mailer)

I am getting this error

NoMethodError: undefined method `deliver_now' for #<Mail::Message:1026960, Multipart: true ...
Did you mean?  deliver
2

There are 2 best solutions below

1
engineersmnky On

Error

As indicated by the error message Mail::Message does not implement #deliver_now. This method is implemented in ActionMailer::MessageDelivery

Issue

You have run into rails magic.

Typical implementation of a Mailer is: (taken from the docs)

class NotifierMailer < ApplicationMailer
  def welcome(recipient)
    attachments.inline['photo.png'] = File.read('path/to/photo.png')
    mail(to: recipient, subject: "Here is what we look like")
  end
end

When you call NotifierMailer::welcome this is actually handled by method_missing because #welcome is an instance method rather than a class instance method, as might be suggested by the call pattern.

The method missing then returns an Actionmailer::MessageDelivery object not an instance of NotifierMailer or a Mail::Message (as the method body would suggest).

Resolution

Redefine your mailer as

class Mailer < ApplicationMailer

  def send_email(emails)
    @emails = emails
    mail(
      to: @emails,
      from: '[email protected]' ,
      subject: 'example'
    )
  end
end

Note: I changed send to send_email because Object already defines send and that means the method_missing would not be invoked.

Then you could:

A) Use the Mailer the way it was designed (Recommended)

Mailer.send_email('[email protected]').deliver_now

B) Utilize the above to modify your service

class MailService 
  
  def initialize(sender:)
    @sender= sender
  end    

  def call(action:, emails: )
    @sender.public_send(action, emails).deliver_now
    # or ActionMailer::MessageDelivery.new(@sender, action, emails)
  end 
end

MailService.new(sender: Mailer).call(action: :send_email, emails: '[email protected]') 

Note: your post said you were using MailService.call(sender: mailer); however this would have resulted in a NoMethodError of its own since call is not a class instance method of MailService, so I assumed this was a simple typo

0
mechnicov On

I suggest such way

class Mailer < ApplicationMailer
  # Do not override Object#send
  def send_email
    # Use parametrized feature
    @emails = params[:emails]

    mail(
      to: @emails,
      from: '[email protected]' ,
      subject: 'example'
    )
  end
end
class MailService
  def initialize(mail:)
    @mail = mail
  end    

  def perform_now
    @mail.deliver_now
  end 

  def perform_later
    @mail.deliver_later
  end 
end
# Prepare mail
mail = Mailer.with(emails: ['[email protected]']).send_email

# Initialize service instance and send prepared mail with deliver_now
MailService.new(mail: mail).perform_now

# ...or deliver_later if needed
MailService.new(mail: mail).perform_later