Sending email with attachments - ruby-on-rails

I've got a mailer that as follows:
class Payments::LateNoticesMailer < AsyncMailer
def notice(payment_id)
#payment = PaymentDecorator.find(payment_id)
#invoice = #payment.invoice
template = "payments/invoices/#{#payment.made_with_type.downcase}/show"
attachments["#{#payment.invoice_filename}.pdf"] =
WickedPdf.new.pdf_from_string( render_to_string( pdf: #payment.invoice_filename,
formats: [:pdf],
template: template,
layout: "layouts/pdf.html"))
mail to: #payment.payer_email,
from: '"RentingSmart" <no-reply#rentingsmart.com>',
cc: #payment.landlord_email,
subject: "*** Your rent payment of #{#payment.amount_due} is overdue ***"
end
end
which I send using SendGrid. Here's my issue, if I open up the email via Gmail, everything works great, the text of the email is there, and the attachment is attached. However, if I open it up using OSX's Mail.app or on my iPhone, I simply get the following:
This is a multi-part message in MIME format...
Anybody have any tips? I think I am following the Rails guides correctly.
Here is the call that I make Payments::LateNoticesMailer.notice(payment.id).deliver

According to the api docs for ActionMailer::Base, if multiple template types are used, all of them are rendered and the mime-type is automatically set to multipart/alternative.
If you add an attachment, the attachment is placed inside a multipart/mixed container.
First question: Are you rendering other types such as text and html? I would not recommend sending out emails with just a pdf part. Even if the text and html parts simply instruct the recipient to open the attachment, they should be there. Ideally, there would be more information in the text/html parts.
Second, are you trying to view the pdf inline, and not as an attachment?
Can you take a look at the raw source of the email and update your post with the structure you're seeing? There will be an initial mime type set in the header. it will look something like this:
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="--==_mimepart_50596418be947_c7223fec9d834d3874256";
charset=UTF-8
Content-Transfer-Encoding: 7bit
This says the parts to follow are not alternative versions of the same information, but instead instruct the email client to display them distinctly.
Later on in the email, your text and html parts should proceeded by something like:
----==_mimepart_50596418be947_c7223fec9d834d3874256
Date: Wed, 19 Sep 2012 06:20:12 +0000
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="--==_mimepart_50596418be468_c7223fec9d834d38741a5";
charset=UTF-8
Content-Transfer-Encoding: 7bit
And finally, the encoded pdf part should have a mime header like:
----==_mimepart_50596418be947_c7223fec9d834d3874256
Date: Wed, 19 Sep 2012 06:20:12 +0000
Mime-Version: 1.0
Content-Type: application/pdf;
charset=UTF-8;
filename=terms.pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename=terms.pdf
With a simple test email I just sent to myself with text, html parts, and a large pdf, I can view the email on my iphone. It shows the html part and an icon that lets me download the pdf.

Some e-mail clients may require an e-mail to have a plain text part in order to display it correctly.

I just ran into this message while converting a Rails app from 2.3 to 3.2. I thought I converted my mailer correctly, but the old version specified content_type: "multipart/mixed" in the options. However, when I removed that, I received the attachments and the HTML and plain-text rendered correctly. I think that having that setting in there overrode whatever Rails does to put in different types of content, which was not what I wanted. Thanks to the original answer for leading me in that direction.

Related

How to decode an email with embedded images in Ruby/Rails Action Mailbox

I am using Action Mailbox, to receive emails in my Rails application. When the emails contain images, I am not sure, how to save the email in the database, decode it, and then display it to the user so that it can be displayed like how we see emails in gmail etc.
I have used this code-
class MyMailbox < ApplicationMailbox
def process
mail_content = mail.body.decoded
post = Post.new(title: mail.subject, content: mail_content)
post.save
end
end
This works well for emails which don't have any images etc. But I want a way to save emails with images. This is apparently to be done by using the different parts of the multipart email, but i am not sure how to proceed.
Finally, I want to display those emails with original look. Let me know how to proceed here. Thanks.
Before you attempt to handle images in the body of an email, you should first check if the email body is multipart or not:
# handle multipart message
body = mail.parts.present? ? mail.parts[0].body.decoded : mail.decoded
What does a multipart email look like? Something like this:
MIME-Version: 1.0
Date: Mon, 8 Nov 2021 19:43:58 +0100
References: <....mail>
In-Reply-To: <....mail>
Message-ID: <...#mail.gmail.com>
Subject: Re: Howdy
From: You <you#example.com>
To: Me <me#example.com>
Content-Type: multipart/alternative; boundary="000000000000376c4305d04b60e2"
--000000000000376c4305d04b60e2
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
Here is my reply to an original message.
--000000000000376c4305d04b60e2
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
<div>
<img src=3D"https://placeholder.img" />
<img src=3D"https://placeholder.img" />
</div>
--000000000000376c4305d04b60e2--
Note that the Content-Type header of the email contains a boundary which is used to separate the email parts. Now that you can see how each part can be a different content type, you have the proper context to understand how to parse each part.
https://github.com/mikel/mail#reading-a-multipart-email
Rails uses the mail gem, so you should reference their docs to understand full functionality. But in short, you can do the following:
if mail.parts.present? && mail.parts[0].content_type == 'text/html'
sanitized = Rails::Html::WhiteListSanitizer.new.sanitize(mail.parts[0], tags: ['img'])
# => "\n <img src=\"https://placeholder.img\">\n <img src=\"https://placeholder.img\">\n\n"
html_doc = Nokogiri::HTML(sanitized)
html_doc.to_s
# => "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n<img src=\"https://placeholder.img\">\n <img src=\"https://placeholder.img\">\n</body></html>\n"
end
To display the original content of the message, just save the part in its entirety. You'll have to either infer the content type store it alongside the message in your database.
Assuming you're saving an HTML message, you can use Nokogiri, which comes with Rails, to generate an HTML document.

Do providing plain text email form ensures us that recipient will get version he can properly show?

By default Rails mailers provides us with two email forms: the html one and the text one. In that post I found such a sentence:
However, if you use html, you should provide an alternative text
version to make sure the readers won't mess the content of your email.
Does it really work that way? And if so, how does it work exactly? I can see that in my mail.body I have only my html version included, so that's not sended in both versions at once. So, in what circumstances my text form can be used when I have set the html one by default? How can I test it?
If the email is sent as a multipart email then the email client can select which format of the email it will display.
Some clients even use both but in different contexts such as phones that display the text version in banners and notifications. Failure to provide a plain text version can often result in the raw html being displayed.
A multipart email is just an email with Content-Type: multipart/alternative; and each part is separated by the string provided in the Boundry header.
X-sender: <sender#sendersdomain.com>
X-receiver: <somerecipient#recipientdomain.com>
From: "Senders Name" <sender#sendersdomain.com>
To: "Recipient Name" <somerecipient#recipientdomain.com>
Message-ID: <5bec11c119194c14999e592feb46e3cf#sendersdomain.com>
Date: Sat, 24 Sep 2005 15:06:49 -0400
Subject: Sample Multi-Part
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="----=_NextPart_DC7E1BB5_1105_4DB3_BAE3_2A6208EB099D"
------=_NextPart_DC7E1BB5_1105_4DB3_BAE3_2A6208EB099D
Content-type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable
Sample Text Content
------=_NextPart_DC7E1BB5_1105_4DB3_BAE3_2A6208EB099D
Content-type: text/html; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable
<html>
<head>
</head>
<body>
<div style=3D"FONT-SIZE: 10pt; FONT-FAMILY: Arial">Sample HTML =
Content</div>
</body>
</html>
------=_NextPart_DC7E1BB5_1105_4DB3_BAE3_2A6208EB099D--
How can I test it?
Depends entirely on the email client / device.
Outlook
Gmail
Thunderbird
If you want to test it with MiniTest/RSpec you can get the different parts of the email with:
message.text_part
message.html_part
You can parse the html_part with Nokogiri or use Capybara to navigate it just like it was a page:
page = Capybara::Node::Simple.new(message.html_part)
assert page.has_content?("Hello World!")

Rails 4/5 Sending Dynamic ActionMailer::Base.mail email With Attachment labeled Noname

I've taken a look at similar posts that mostly deal with sending an attachment by creating a view and controller, such as:
PDF attachment in email is called 'Noname'
but I've got a process that generates files in the background dynamically and need to attach it to a recipient list using ActionMailer::Base.mail. Below is the code:
def send_email(connection)
email = ActionMailer::Base.mail(to: connection['to'], from: connection['from'], subject: 'Sample File', body: "<p>Hello,</p><p>Your data is ready</p>", content_type: 'multipart/mixed')
email.cc = connection['cc'] if connection['cc'].present?
email.bcc = connection['bcc'] if connection['bcc'].present?
#files.each do |file|
report_file_name = "#{#start_time.strftime('%Y%M%dT%I%m%s')}_#{file[0]}.xlsx"
file_location = "#{Rails.root}/tmp/#{report_file_name}"
email.attachments[report_file_name] = File.open(file_location, 'rb'){|f| f.read}
end
email.deliver if email
end
I can see in the logs that it's sending with the content but assume it's sending as Noname because it can't find the view. Any way to get this to work successfully?
Below is the sample output:
Sent mail to sample#sample.com (383.9ms) Date:
Thu, 13 Oct 2016 08:47:30 -0400 From: Sample To:
Recipient Message-ID:
<57ff326270f15_421f1173954919e2#ulinux.mail> Subject: Sample File
Mime-Version: 1.0 Content-Type: multipart/mixed; charset=UTF-8
Content-Transfer-Encoding: 7bit
-- Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;
filename=20161012T08101476259208_Data.xlsx
Content-Transfer-Encoding: base64 Content-Disposition: attachment;
filename=20161012T08101476259208_Data.xlsx Content-ID:
<57ff326270f15_421f1173954919e2#ulinux.mail>
UEsDBBQAAAAIAO.. ... ...ADUFQAAAAA=
Update - I noticed if I use email.content_type = 'text/plain' - the attachment comes through successfully. For me, this works, though I'd appreciate later being able to style my emails with HTML
I presume this works because it prevents Rails from its usual gleaning/autointerpreting process. I'd certainly like to see a multipart/mixed or html compatible version work here though.
Update 2 This only fixed the issue artificially in the rails_email_preview gem, which renders the emails to a new tab in development. In production, this simply and understandably prints the details and the presumably base64-encoded file, so question remains open.
I have meet this problem too, after some investigation, it seems in Rails 4, you can't call attachments method after calling mail method, otherwise the content_type of the mail message object won't have boundary information so that the attachments part can't be parsed correctly in the received email.
I think digging into the actionmailer source code and you should be able to find a solution, either by override the default mail method or set the correct boundary info manually.
But for quick resolving this problem, I thought out a not elegant work around by using meta programming: define a delegation class which inherits ActionMailer::Base.
class AnyMailer < ActionMailer::Base
# a delegation mailer class used to eval dynamic mail action
end
Then eval this class with defining an arbitrary method to perform the email sending.
def send_email(connection, files)
AnyMailer.class_eval do
def any_mailer(connection, files)
files.each do |file|
report_file_name = :foo
file_location = :bar
attachments[report_file_name] = File.open(file_location, 'rb'){|f| f.read}
end
mail(to: connection['to'], from: connection['from'], subject: 'Sample File', body: "<p>Hello,</p><p>Your data is ready</p>")
end
end
AnyMailer.any_mailer(connection, files).deliver_now
end
Attention, you don't need to specify the content_type as 'multipart/mixed', ActionMailer will handle it correctly. I tried to specify it explicitly but get messed up email content instead.
This has been driving me insane.
Make sure you have a well formed .html template and a .text template if you are using mailer views.
Minimal errors in either of them will render the entire email as a noname attachment.
You might don't have mailer.text.erb file along with mailer.html.erb file.
Add it and your mail will be multipart.

Mailing attachments using the ruby mail gem

I am using the Mail gem to send out emails with attachments from my application. Specifically, I have identical code living on my devbox and a server machine which is used to report a pdf file. However, pdfs sent as attachments from my devbox show up in Thunderbird and other clients fine, whereas attachments from the server show up only in SOME (not thunderbird) mail clients fine. I've opened the email source to compare the two and try to get some insight into the issue:
Devbox:
--_av-F3fhmdkLZDU59RCz4nm2gw--
--_av-ycGAp26idM_6kl-0lMcYmw
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9MZW5ndGggNjc4L0ZpbHRlci9G
bGF0ZURlY29kZT4+c3RyZWFtCnic7Zhdb9owFIZ7S37FuZm0SZA53wkSF1Bg
...
MzBhN2RlZDE0ZmJkZDMwNzU1MzU2Y2ZhPjxlYTM0OTMzOTEwN2RmYzI1NTJh
NDE5NTJmNDhmN2MwMD5dL0luZm8gMzMgMCBSL1NpemUgMzQ+PgpzdGFydHhy
ZWYKNDE2NjQKJSVFT0YK
--_av-ycGAp26idM_6kl-0lMcYmw--
Server:
----==_mimepart_561d49a469c4d_33ee3fea57ad63b8624ae
Content-Type: application/pdf;
charset=UTF-8;
filename=report.pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename=report.pdf
Content-ID: <561d49a46bc9f_33ee3fea57ad63b862596#autopilot-demo-east-web-1.mail>
JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9MZW5ndGggNjgwL0ZpbHRlci9G
bGF0ZURlY29kZT4+c3RyZWFtCnic7Zhdb9owFIZ7S37FuZm0SSVznJAPJC4o
oWs7qrVdql500xTAgNt8QGKg3a+fnQ5RbR3ttnYziQkR9jk+Pu95bEcKM+0V
nPjWcQ8WJMtpmjTBdI3RjQEnWcp9R/77tcfQbR19MW1u//Bxbe7RZH4Dpo51
..
N2I0MjdlNTE4ZTNmZGNmOGE5YzY1NTRjMzc+PGE1N2MzNmNkNTE0ZTQ1Mzlk
ZjY3OTU2ODZkY2M2ZDEzPl0vSW5mbyAzMyAwIFIvU2l6ZSAzND4+CnN0YXJ0
eHJlZgo0MTY2NgolJUVPRgo=
----==_mimepart_561d49a469c4d_33ee3fea57ad63b8624ae--
The simple line adding the attachment (Rails):
email.add_file filename: "sample.pdf", content: File.read("/public/sample.pdf")
Off the bat, the section identifiers are different (_av vs mimepart) and the way each attachment is encoded is also different after a few lines. My question is, what could be causing certain clients not to respect the "mimepart" sections as attachments? Beyond that, the next question would be why the same code would generate different output, but if I know the underlying issue I may be able to solve that myself. Thanks!

Sending 1 attachement, recipients reports 2 attachements (ATT0001.c added) - Rails 3, ActionMailer

To exchange data with another system we send the data as an email attachment to a dedicated address. The email is generated using ActionMailer v3.2.12.
The problem is that when the email arrives at its destination, a redundant attachment named ATT00001.c is a part of the email, in addition to the attachment we created. This causes issues with the import routine at the other end.
A big part of the problem is that we know almost nothing about how the email is being handled at the destination . We also dont know what type of email server is in use and dont have access to check what the email actually looks like when it arrives. We can send it to one of our own addresses and it looks fine there.
I know this is not a lot to go on, but perhaps one of you guys have seen these ATT00001-attachments being added to machine generated emails before.
config.action_mailer.smtp_settings
address: smtp.<mailprovider>.com
port: 587
domain: ourdomain.com
authentication: login
user_name: <removed>
password: <removed>
enable_starttls_auto: false
Update:
We've been able to obtain a copy of the problematic email and it shows the email body rendered after the attachment as an attachment of its own.
We've tried setting ActionMailer's parts_order to make sure the attachment is generated after the email body, it did not help.
Update2:
Sending to my gmail account and showing original raw data I get this.
SENT MAIL
in receipt response from recipient to the correct attachment (the autocreated one creates an error log entry)
(...) cut: to from and through email header information
Mime-Version: 1.0
Content-Type: multipart/mixed;
charset=UTF-8
Content-Transfer-Encoding: 7bit
--
Date: Thu, 28 Feb 2013 12:15:23 +0100
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="thefile.mscons"
Content-ID: <512f3c4b6e875_a8f756dcc642fe#bjorns_arch.mail>
VU5BOisuPyAnVU5CK1VOT0M6Mys3MDgwMDAzNDExNzE2OjE0OlRJTUVSKzcw
... many more lines like this ...
ODAwMDUwNTEyMTc6MTQ6VElNRVIrMTMwMjI4OjEyMTUrUE9XRVNUMTMwMjI4
----
This is with body nil in actionmailer
Next is a RESPONSE from the recipient system, sent to my gmail. It's a receipt on the correct attachment (the extra attachement generates an error, flushing their system)
RECEIVED MAIL
(..) unintersting header stuff with addresses
Content-Disposition: attachment;
filename="afilename.txt"
Content-Transfer-Encoding: base64
Content-Type: Application/EDIFACT; charset="iso-8859-1"
Mime-Version: 1.0
Date: Sat, 16 Feb 2013 11:07:10 +0100
From: ediel#example.com
To: ***#gmail.com
Subject: thesubject
Message-ID: <511f5a53.850a700a.2fa0.2a0eSMTPIN_ADDED_BROKEN#mx.google.com>
X-TM-AS-Product-Ver: IMSS-7.0.0.6298-6.8.0.1017-19380.002
X-TM-AS-User-Approved-Sender: Yes
X-Greylist: Sender is SPF-compliant, not delayed by
milter-greylist-4.0 (isp-app27-vm.isp.example.com [213.239.116.46]);
Sat, 16 Feb 2013 11:07:11 +0100 (CET)
X-ExampleIKT-MailScanner-Information: Please contact the ISP for more information
X-ExampleIKT-MailScanner-ID: r1GA7BqD021150
X-ExampleIKT-MailScanner: Found to be clean
X-ExampleIKT-MailScanner-From: ediel#example.com
X-Spam-Status: No
VU5BOisuPyAnVU5CK1VOT0M6Mys3MDgwMDA1MDUxMjE3OjE0OlRJTUVSKzcwODAwMDM0MTE3MTY6
.. more..
pUSU1FUisxJ1VOVCszKzEnVU5aKzErMjAxMzAyMDAyNDg1Nzcn
something suspicious with the Content-Type? Is a new (empty) attachment generated from the stuff prior to -- in the sent email?
I believe this is to do with inline attachments and Exchange server. Some clients, Apple Mail in particular allow you to add inline attachments, that is, a MIME attachment sandwiched in between text/body parts of an email. Exchange server expects that all attachments appear after any text portion of a mail.
Everything after the attachment in your mail gets treated as an attachment, so the body gets stuffed into a file and named as you reported it to be named. Seeing as you're using ActionMailer, see this answer and possible this answer, which explains that you need to switch the order of the lines of code, and possibly play with some other settings.
Our problem is solved, though unfortunately I cant say what caused the redundant attachment. We worked around it by sending a non-multipart email that contained only the attachment. This solution obviously wont work for people who need to send a multipart email.
Sending non-multipart email with an attachment in Rails is not straight forward. You cant use the attachment helper method and a blank body, you need to put the attachment content in the email body and manually specify the disposition.
class MailMan < ActionMailer::Base
def test
attachment_content = "my attachment"
disposition = "attachment; filename=\"test.txt\""
mail(body: attachment_content, content_disposition: disposition)
end
end

Resources