rmcsharry
rmcsharry

Reputation: 5562

Rails API - sending a customized email without using a view, how can I validate the data?

I have a Rails API which will receive requests that contain email details (to, subject, from etc) in the request of the body.

Since these emails are sent to customers, I set up the route, controller action and mailer as follows:

routes.rb

resource :customers do
  member do
    post 'send_checklist'
  end
end

customers_controller.rb

def send_checklist
  CustomerMailer.send_checklist(customer_params[:email]).deliver_now
end

customer_mailer.rb

class CustomerMailer < ApplicationMailer
  def send_checklist(data)
    mail(
      to: data.to,
      from: '[email protected]',
      subject: data[:subject],
      body: data[:body],
      content_type: 'text/html'
    )
 end
end

json structure in body of post request

"customer": { 
  "mail": {
    "to":"[email protected], [email protected]"
    "subject":"Test message"
    "body":"This is the email message"
  }
}

This works, however I cannot validate that the 'to' property has at least one email address and that each email address is a valid email.

How can I validate the 'to' property and return a 422 unprocessible entity error from the controller action?

So in psuedo-code the action would be something like this:

def send_checklist
  if data_is_valid
    send_email
    respond with 200 ok (or 204 No Content?)
  else
    respond with 422
  end
end

I am thinking that somehow this validation logic should go into the CustomerMailer class itself, or maybe I should create a Mail model class with attr_accessors (to, subject, body) with validators? Not sure what is the correct solution, hence the question.

EDIT - proposed solution, which fails

Based on the first two proposed answers, I wrote this code:

customers_controller.rb

  def send_checklist
    from_user = "#{current_api_user.name} <#{current_api_user.email}>"
    if CustomerMailer.emails_valid?(customer_params[:mail][:to])
      CustomerMailer.send_checklist(from_user, customer_params[:mail]).deliver_now
      render json: nil, status: :ok
    else
      render json: ErrorSerializer.serialize('Invalid email address found'), status: :unprocessable_entity
    end
  end

customer_mailer.rb

  def emails_valid?(emails_list)
    Rails.logger.debug "*** HERE ***" + emails_list
    emails = emails_list.split(/\s+,\s+/)
    Rails.logger.debug emails
    is_valid = true
    emails.each do |email|
      Rails.logger.debug email
      is_valid = false unless email=~ /([^\s]+)@([^\s]+)/ 
    end
    return is_valid
  end

The emails_valid? is not called by the controller - nothing appears in the log from that method. It seems the controller does not call it and the if statement always returns true.

On further investigation, it seems I cannot put the emails_valid? method in the mailer class because of the way ActionMailer works as explained here.

EDIT - clarification

Just in case it is not clear, this is not a normal mail sending flow that you see in a Rails application:

  1. This is an API, there are no rails forms.

  2. The client sends a post request with the JSON shown above - the "To" field contains a string of comma-separated email addresses.

  3. There is NO model here. None of the data sent in the request is stored in the database, it simply used to construct and send an email.

  4. The only 'relationship' involved is that the post request is sent to customers/id - this is so later I can log that an action (send_checklist) was taken for this customer.

Upvotes: 0

Views: 1063

Answers (2)

ruby_newbie
ruby_newbie

Reputation: 3285

Assuming these users aren't existing users in your system this is how I would approach it.

in your Controller:

def send_checklist
    from_user = "#{current_api_user.name} <#{current_api_user.email}>"
    render json: ErrorSerializer.serialize('Invalid email address found'), status: :unprocessable_entity unless email_valid?(customer_params[:mail][:to])

    CustomerMailer.send_checklist(from_user, customer_params[:mail]).deliver_now
    render json: nil, status: :ok
  end
private





  def email_valid?(emails_list)
    emails = emails_list.split(/\s+,\s+/)
    true unless emails.any? {|email| email=!~ /([^\s]+)@([^\s]+)/ }
  end

Or something to that effect. I am also not sure why you would need to validate multiple email addresses based on your code but I noticed your solution was taking a list so I made mine take a list as well. Your use case is a bit uncommon I think because most of the time the email is validated on saving to the db then the mail is sent after that but you don't have that step. You may also need to cast customer_params[:mail][:to] to an array if it is not coming over as such.

Upvotes: 1

Sakthivel M
Sakthivel M

Reputation: 39

If you've a model to store email then you could check the param by querying the model

emails_arr = params[:to].split(",").map(&:strip)

This will give you an array of email from the params. Then you could use query to check whether the DB has the email present in the array. Assuming you've a Customer model you could do

Customer.where(email: email_arr).pluck(:email).join(",")

Pass this to the mailer's to param. If you don't have a Customer model to query then you could use simple regex like below to check the validity of the email.

/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i

Ruby Regex docs for your reference

Upvotes: 3

Related Questions