Rails mailer and mailer views in subfolder not working - ruby-on-rails

I have a mailer that I can see in my log is getting sent, but the email body does not contain anything from the mailer view.
It's due to the fact that I've put things in subfolders and i've tried using :template_path in my mail function but to no avail.
app/mailers/marketing/marketing_mailer.rb
class Marketing::MarketingMailer < ActionMailer::Base
require 'mail'
address = Mail::Address.new "test#example.com" # ex: "john#example.com"
address.display_name = "Text" # ex: "John Doe"
# Set the From or Reply-To header to the following:
address.format # returns "John Doe <john#example.com>"
default from: address
# Sends an email when someone fills out the contact form
def contact(name, email, message)
#name = name
#email = email
#message = message
mail(:subject => "Test", :to => 'test#example.com', :reply_to => #email) # <== I've tried using :template_path => 'marketing', 'marketing/marketing_mailer', etc, but none worked.
end
end
/app/views/marketing/marketing_mailer/contact.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<p>Name: <%= #name %></p>
<p>Email: <%= #email %></p>
<p>Message: <%= #message %></p>
</body>
</html>
I noticed that devise has mailer views inside /views/devise/mailers/... so I know it's possible, but i'm not sure how.

Have you tried mail with {:template_path => 'marketing/marketing_mailer, :template_name =>'contact'} ?
The template name probably expects the namespace in the filename somehow

Does it work without the template Rendering
class Marketing::MarketingMailer < ActionMailer::Base
def welcome_email(user, email_body)
mail(to: user.email,
body: email_body,
content_type: "text/html",
subject: "Already rendered!")
end
end

I came back to this project today and it seems to be finding the template today no problem without having to specify the template_path.
As sad as this is, apparently all I needed to do was stop and restart my server for my mailer views to get picked up. Since other views get picked up without stopping and starting my server, i'm very surprised by this.

Since your file is in the format of .html.erb, you may have to specify that you want to use this format. Like so:
mail(:subject => "Test", :to => 'test#example.com', :reply_to => #email) do
format.html
end
Usually, your template should just be picked up automatically, since the names match up, but there's probably a second file by the same name ending in .text.erb or some other thing that gets in the way of template look-up.
EDIT1 (wild guess)
Aside from template lookup issues, the only other source of problems I can imagine, would be your instance variables. Maybe #message is supposed to be something else and you're interfering with internals, so renaming it might help. According to the code on apidock, mail is using #variables at certain places. This is far fetched though, I admit.
EDIT2 (explanation of the problem faced here)
The behavior behind the actual problem - not finding a new template until server restart - works like this:
A template gets looked up but at that time, it's not present yet. The server doesn't find it, but remembers the fact that it's not there. Then won't look for it again until the next restart, even though you added it.
So, restarting will fix this.
Have you tried turning it off and on again?

Related

Rails 4 - Postmark integration

I'm trying to figure out how to send transactional email from my Rails 4 app.
I have found tutorials for the postmark gem, but I'm struggling to close the gaps between what's assumed in the tutorials (where to do the suggested steps!) and what I know.
I have installed both the ruby and the rails gems in my gemfile:
gem 'postmark-rails', '~> 0.13.0'
gem 'postmark'
I have added the postmark config to my config/application.rb:
config.action_mailer.delivery_method = :postmark
config.action_mailer.postmark_settings = { :api_token => ENV['POSTMARKKEY'] }
I want to try to make and use email templates in postmark.
The instructions in the postmark gem docs say I need to:
Create an instance of Postmark::ApiClient to start sending emails.
your_api_token = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
client = Postmark::ApiClient.new(your_api_token)
I don't know how to do this step? Where do I write the second line? I have my api token stored in my config. I don't know how to make an instance of the postmark api client.
Can anyone point me to next steps (or a more detailed tutorial)?
After you have installed the gems, you need to create a Mailer. I presume you have already configured the API keys etc. in the correct manner, so I will concentrate on actually sending out a templated / static email.
Lets create app/mailers/postmark_mailer.rb file with the following contents.
class PostmarkMailer < ActionMailer::Base
default :from => "your#senderapprovedemail.com>"
def invite(current_user)
#user = current_user
mail(
:subject => 'Subject',
:to => #user.email,
:return => '74f3829ad07c5cffb#inbound.postmarkapp.com',
:track_opens => 'true'
)
end
end
We can then template this mailer in the file app/views/postmark_mailer/invite.html.erb Let's use the following markup to get you started.
<p>Simple email</p>
<p>Content goes here</p>
You can write it like any other .html.erb template using tags, HTML and alike.
To actually send this email out you need to place an action in your controller, in the following manner.
PostmarkMailer.invite(current_user)
Alternatively, if you want this email to be sent upon visiting homepage, it would most likely look like this:
app/controllers/home_controller.rb with content
class HomeController < ApplicationController
# GET /
def index
PostmarkMailer.invite(current_user)
end
end
and corresponsing route
config/routes.rb with content
root :to => 'home#index'
I hope this answers your question.

How to modify actionmailer email html_part before sending

I have everything at the point where I'm about to send out the email but I need to modify all links to include Google Analytics attributes. The problem is that if I try and read/write the html_part.body of the email, the entire html string somehow becomes encoded and doesn't display the email properly (i.e. <html> becomes <html>). I have logged the html_part.body.raw_source in the logger and it shows as proper unencoded HTML, it's only when the email is actually sent does the encoding occur.
EBlast.rb (ActionMailer)
def main(m, args={})
# Parse content attachment references (they don't use helpers like the layout does)
# and modify HTML in other ways
m.prep_for_email self
#email = m # Needed for helper methods in view
mail_args = {
:to => #user.email,
:subject => m.subject,
:template_path => 'e_blast',
:template_name => 'no_template'
}
mail_args[:template_name] = 'main' if m.needs_template?
m.prep_for_sending mail(mail_args)
end
Email.rb
def prep_for_sending(mail_object)
if mail_object.html_part
# If I simply do a 'return mail_object', the email sends just fine...
# but the url trackers aren't applied.
# Replace the content with the entire generated html
self.content = mail_object.html_part.body.decoded
# Add Google analytics tracker info to links in content
apply_url_tracker :source => "Eblast Generator", :medium => :email
# Replace the html_part contents
mail_object.html_part.body = content
# At this point, mail_object.html_part.body contains the entire
# HTML string, unencoded. But when I send the email, it gets its
# entities converted and the email is screwed.
end
# Send off email
mail_object
end
Looks like I'm answering my own question again - I'm on a roll this week.
Apparently setting the body directly creates some odd attribute called 'body_raw' instead of replacing the raw_contents of the html_part. So basically I ended up having a duplicate part embedded in the mail object (I don't know why it does this). Creating a separate Mail::Part and assigning it to html_part just added another part instead of replacing html_part! WTF?!
New Edit: Scratch my last remark about String.replace. It looked like it was working but when I went to another computer and tested it, the same problem of duplication occurred.
Another Edit: Finally?
Before I executed the apply_url_tracker method I had reset the content of the email (for the purposes of changing all the links in the rendered view). I don't have any idea why that screws with the Mail object considering the message should already have been rendered but changing my methodology to the following has fixed the duplication of email parts and their subsequent 'reencoding'. I no longer change the content attribute, I only change the html_part:
def prep_for_sending(message)
if message.html_part
# Replace the html raw_source
message.html_part.body.raw_source.replace apply_url_tracker(message.html_part.body.decoded, :source => "Eblast Generator", :medium => :email)
end
message
end
Clarification:
Even though the call to mail() produces a Mail object with fully rendered HTML/Text parts (i.e., fully rendered views), changing the attribute that is USED by those views (in my case, the 'content' attribute) screws up the final send. Don't modify your model before sending, JUST MODIFY THE MAIL PART DIRECTLY.

What is the right way to embed image into email using Rails?

What is the right way to embed an image into email using Rails?
I combined the answer from Oksana with a custom helper approach and got the following to work quite nicely.
app/helpers/email_helper.rb
module EmailHelper
def email_image_tag(image, **options)
attachments[image] = File.read(Rails.root.join("app/assets/images/#{image}"))
image_tag attachments[image].url, **options
end
end
app/mailers/base_mailer.rb
class BaseMailer < ActionMailer::Base
helper(EmailHelper)
end
app/mailers/my_mailer.rb
class MyMailer < BaseMailer
def send_my_mail(email)
mail to: email, subject: "My Subject"
end
end
Then for example where I want to attach the company logo in my email layout file I would use
app/views/layouts/email.html.erb
<%= email_image_tag("company_logo.png") %>
Note the **options makes the tag more extensible but it will only work in ruby >=2. To make this work in ruby < 2 you will have to use the older way of handling key word options.
Update 03/25/2022: Rails 6 no longer supports add_template_helper, and now replaces it with helper, as explained by this answer that links to the commit that did so.
RAILS 5
In your mail method add your inline attachment pointing to your image:
class ConfirmationMailer < ActionMailer::Base
def confirmation_email
attachments.inline["logo.png"] = File.read("#{Rails.root}/app/assets/images/logo.png")
mail(to: email, subject: 'test subject')
end
end
Then in your mail html view an image_tag with the attachment url:
<%= image_tag(attachments['logo.png'].url) %>
Adding onto Oksana and tdubs' answers
The module tdubs wrote works on desktop, but for the mobile gmail client, the images appeared as attachments. To fix this, do this for the
app/helpers/email_helper.rb
module EmailHelper
def email_image_tag(image, **options)
attachments[image] = {
:data => File.read(Rails.root.join("app/assets/images/emails/#{image}")),
:mime_type => "image/png",
:encoding => "base64"
}
image_tag attachments[image].url, **options
end
end
For the rest, follow tdubs's answer.
After a lot of research i have found very cleaner way to embed image in email.
Just add following line in production.rb and development.rb
config.action_mailer.asset_host = 'YOUR HOST URL'
In your view embed image by using following code.
<%= image_tag('My Web Site Logo.png') %>
Note: Make sure to update YOUR HOST URL and My Web Site Logo.png in
above code.
For basic details of usage of Action Mailer, please refer to ActionMailer::Base.
Copy pasted from here
http://api.rubyonrails.org/classes/ActionMailer/Base.html#class-ActionMailer::Base-label-Inline+Attachments
Inline Attachments
You can also specify that a file should be displayed inline with other HTML. This is useful if you want to display a corporate logo or a photo.
class Notifier < 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
And then to reference the image in the view, you create a welcome.html.erb file and make a call to image_tag passing in the attachment you want to display and then call url on the attachment to get the relative content id path for the image source:
<h1>Please Don't Cringe</h1>
<%= image_tag attachments['photo.png'].url -%>
As we are using Action View's image_tag method, you can pass in any other options you want:
<h1>Please Don't Cringe</h1>
<%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%>
In rails 6, if you use the solution of #Tyron, you'll need to replace add_template_helper with helper in the BaseMailer. So, it becomes:
class BaseMailer < ActionMailer::Base
helper(EmailHelper)
end
I don't know much about rails, but I've worked on projects in C# that create emails and then insert them in a users inbox via the Google APIs. To create the email, I had to generate the email string from scratch. If you enable multipart for the email, then the image bytes will be included in a multipart section using base64 encoding.
You may want to check out the TMail and RubyMail packages to see if they support these actions for you.

rails mailer with different layouts

I use one layout for all my emails in my Notifier model (20+ emails)... however sometimes I just want to send a plain text email with no layout or html at all. I can't seem to be able to figure out how? If I try to send a plain text email i still get the layout, and all the HTML in the email.
I'm using Rails 2.3.8.
I read about this monkey patch here... but it seemed to indicate a newer version of rails had over come this? And I don't really wanna monkey patch if I can avoid one.
Rails - setting multiple layouts for a multipart email with mailer templates
layout "email" # use email.text.(html|plain).erb as the layout
def welcome_email(property)
subject 'New Signup'
recipients property.email
from 'welcome#test.com'
body :property => property
content_type "text/html"
end
def send_inquiry(inquire)
subject "#{inquire.the_subject}"
recipients inquire.ob.email
from "Test on behalf of #{inquire.name} <#{inquire.email}>"
body :inquire => inquire
content_type "text/plain"
end
I also have 2 files.
email.text.html.erb
email.text.plain.erb
It always uses text.html.erb... even if the content_type is "text/plain"
edit: Figured it out, the layouts follow a different naming scheme to the email templates. Just rename them as follows:
layout.text.html.erb => layout.html.erb
layout.text.plain.erb => layout.text.erb
I also made the mistake of manually defining the parts, if you use this:
part :content_type => 'text/plain',
:body => render_message('my_template')
Then Rails can't determine the content_type for your part and it assumes it's HTML.
After I changed those two things it worked for me!
original reply follows..
I've struggled with this question many times in the past, usually ending up with some sort of non-dry quick and dirty solution. I always thought I was the only one with this problem because Google turns up exactly nothing useful on the subject.
This time I decided to dig into Rails to figure it out but so far without much success, but maybe my findings will help someone else figure this out.
What I found was that in ActionMailer::Base the #render_message method is tasked with determining the proper content_type and should assign it to #current_template_content_type. #default_template_format then either returns the proper mime type for the layout or, if #current_template_content_type isn't set, it will default to :html.
This is what ActionMailer::Base#render_message looks like in my app (2.3.5)
def render_message(method_name, body)
if method_name.respond_to?(:content_type)
#current_template_content_type = method_name.content_type
end
render :file => method_name, :body => body
ensure
#current_template_content_type = nil
end
The trouble is that method_name appears to be a string (the name of the local view, in my case "new_password.text.html") and strings of course do not respond_to #content_type, meaning #current_template_content_type will always remain nil, and so the #default_template_format will always default to :html.
Not much closer to an actual solution, I know. The ActionMailer internals are just too opaque for me.
OK, not sure if this works, but it seems the default content_type is text/plain, so you would only need to set the content type if you want something other than text/plain.
Try this:
def send_inquiry(inquire)
subject "#{inquire.the_subject}"
recipients inquire.ob.email
from "Test on behalf of #{inquire.name} <#{inquire.email}>"
body :inquire => inquire
end
I still think you should consider this:
layout "email", :except => [:send_inquiry]
I would use the above because the plain text email does not seem to have a 'layout', only the actual content you want to send.
I found this that I think could be useful.
http://blog.blazingcloud.net/2009/11/17/simple-email-form-with-actionmailer/
He makes use of renaming the view templates for different content types.

Problem with url_for and named routes in ActionMailer View: "Need controller and action"

I'm attempting to provide a confirmation link in my user welcome email and I'm getting the following Rails error:
Need controller and action!
It makes a fuss about this line:
<p>Please take a moment to activate your account by going to:
<%= link_to confirm_user_url(:id => #user.confirmation_code) %>.</p>
In my development.rb environment, I have the following line:
config.action_mailer.default_url_options = {
:host => "localhost", :port => 3000
}
There's no problem with the #user variable. I've tested the email with things like #user.username and #user.confirmation_code. I'm only getting trouble with url_for and named routes like confirm_user_url.
When I check my routes with rake routes, confirm_user shows up, so it's not an issue with the named route not existing.
I can't seem to figure it out. What gives?
ActionMailer isn't a real Rails controller, so routes aren't available in your mailer templates. You need to set up instance variables in your mailer class to pass values to the template.
eg. if your mailer is SampleMailer:
class SampleMailer < ActionMailer::Base
def confirmation_mail(user)
subject 'Confirmation'
recipients user.email
from 'sample#example.com'
sent_on Time.now
body :greeting => 'Sample Greeting', :email => user.email,
:confirm_user_url => confirm_user_url(:id=>#user.confirmation_code)
end
end
Then in the template:
<%= link_to "Confirmation link", #confirm_user_url %>
This is explained somewhat cryptically in the api in the "ActionMailer" section, and in more detail in Agile Web Development with Rails, 3rd Ed., Chapter 25.
Since having a clickable url is what you said in your where after in your comment, here you go macek
<p>Please take a moment to activate your account by going to:
<%= auto_link confirm_user_url(:id => #user.confirmation_code) %>.</p>
I've used the auto_link helper in some of my mailers to make urls clickable and it has worked out pretty well. I think that does email addresses also, check out the full api for all the options. Enjoy.

Resources