Reputation: 5562
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:
This is an API, there are no rails forms.
The client sends a post request with the JSON shown above - the "To" field contains a string of comma-separated email addresses.
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.
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
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
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