I am using IMAP to receive some emails from gmail and then parse them with the new Rails 3 ActionMailer receive method.
raw_email = imap.uid_fetch(uid, ['RFC822']).first.attr['RFC822']
email = UserMailer.receive(raw_email)
email is now a Mail::Message Class and to:, :from and :subject works fine. However I can't figure out how to get the plain text from the body. Before Rails 3 I could use the tmail_body_extractors plugin to get the plain body.
How would you do this with Rails 3?
Jakobinsky:
email.body.raw_source is a bit closer be still gives me a lot of garbage.
"--001636310325efcc88049508323d\r\nContent-Type: text/plain;
charset=ISO-8859-1\r\nContent-Transfer-Encoding:
quoted-printable\r\n\r\nThis is some body
content\r\n\r\n--=20\r\nVenlig Hilsen\r\nAsbj=F8rn
Morell\r\n\r\n--001636310325efcc88049508323d\r\nContent-Type:
text/html; charset=ISO-8859-1\r\nContent-Transfer-Encoding:
quoted-printable\r\n\r\naf mail-- Venlig
HilsenAsbj=F8rn
Morell\r\n\r\n--001636310325efcc88049508323d--"
Should I roll my own helpers for cleaning up the raw_source or is there already a method ,gem or helper for that?
See the answer on this question:
Rails - Mail, getting the body as Plain Text
In short the solution is:
plain_part = message.multipart? ? (message.text_part ? message.text_part.body.decoded : nil) : message.body.decoded
But I recommend reading the full answer as it is very good and comprehensive.
You can extract the plain body from body.parts. There is still however some headers left in the body:
if body.multipart?
body.parts.each do |p|
if p.mime_type == "text/plain"
body = p.body
end
end
end
body.to_s
You should be able to retrieve the body as simple as email.body.raw_source.
I haven't tested it myself, but you can take a look as the source code for ActionMailer:: Message#body.
Related
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.
I'm trying to follow this solution to add a params parser to my rails app, but all that happens is that I now get the headers but no parameters from the body of the JSON request at all. In other words, calling params from within the controller returns this:
{"controller"=>"residences", "action"=>"create",
"user_email"=>"wjdhamilton#wibble.com",
"user_token"=>"ayAJ8kDUKjCiy1r1Mxzp"}
but I expect this as well:
{"data"=>{"type"=>"residences",
"attributes"=>{"name-number"=>"The Byre",
"street"=>"Next Door",
"town"=>"Just Dulnain Bridge",
"postcode"=>"PH1 3SY",
"country-code"=>""},
"relationships"=>{"residence-histories"=>{"data"=>nil},
"occupants"=>{"data"=>nil}}}}
Here is my initializer, which as you can see is almost identical to the one in the other post:
Rails.application.config.middleware.swap(
::ActionDispatch::ParamsParser, ::ActionDispatch::ParamsParser,
::Mime::Type.lookup("application/vnd.api+json") => Proc.new { |raw_post|
# Borrowed from action_dispatch/middleware/params_parser.rb except for
# data.deep_transform_keys!(&:underscore) :
data = ::ActiveSupport::JSON.decode(raw_post)
data = {:_json => data} unless data.is_a?(::Hash)
data = ::ActionDispatch::Request::Utils.deep_munge(data)
# Transform dash-case param keys to snake_case:
data = data.deep_transform_keys(&:underscore)
data.with_indifferent_access
}
)
Can anyone tell me where I'm going wrong? I'm running Rails 4.2.7.1
Update 1: I decided to try and use the Rails 5 solution instead, the upgrade was overdue anyway, and now things have changed slightly. Given the following request:
"user_email=mogwai%40balnaan.com
&user_token=_1o3Kpzo4gTdPC2bivy
&format=json
&data[type]=messages&data[attributes][sent-on]=2014-01-15
&data[attributes][details]=Beautiful+Shetland+Pony
&data[attributes][message-type]=card
&data[relationships][occasion][data][type]=occasions
&data[relationships][occasion][data][id]=5743
&data[relationships][person][data][type]=people
&data[relationships][person][data][id]=66475"
the ParamsParser middleware only receives the following hash:
"{user":{"email":"mogwai#balnaan.com","password":"0h!Mr5M0g5"}}
Whereas I would expect it to receive the following:
{"user_email"=>"mogwai#balnaan.com", "user_token"=>"_1o3Kpzo4gTdPC2b-ivy", "format"=>"5743", "data"=>{"type"=>"messages", "attributes"=>{"sent-on"=>"2014-01-15", "details"=>"Beautiful Shetland Pony", "message-type"=>"card"}, "relationships"=>{"occasion"=>{"data"=> "type"=>"occasions", "id"=>"5743"}}, "person"=>{"data"=>{"type"=>"people", "id"=>"66475"}}}}, "controller"=>"messages", "action"=>"create"}
The problem was caused by the tests that I had written. I had not added the Content-Type to the requests in the tests, and had not explicitly converted the payload to JSON like so (in Rails 5):
post thing_path, params: my_data.to_json, headers: { "Content-Type" => "application/vnd.api+json }
The effects of this were twofold: Firstly, since params parsers are mapped to specific media types then withholding the media type meant that rails assumed its default media type (in this case application/json) so the parser was not used to process the body of the request. What confused me was that it still passed the headers to the parser. Once I fixed that problem, I was then faced with the body in the format of the request above. That is where the explicit conversion to JSON is required. I could have avoided all of this if I had just written accurate tests!
I`ve written an SendMail script in basic Ruby and it works very well. The problem is if the script sends to an eMail address like "notavliable1" i get the SMTP error that the receipt is not reachable and the script stops.
I need an exception to filter or an REGEX for the Mailaddress.
Here is the snippet:
require 'mail'
mail = Mail.new do
from 'peter#lustig.de'
to ("#{send.toAdress}")
subject ("#{send.subject}")
html_part do
content_type 'text/html; charset=UTF-8'
body ("#{send.body}")
end
end
How do i tell my snippet to skip an invalid "send.toAdress" ?
I´am using the 'mail' gem.
Using an exception is probably the best practice, since a REGEX will not find every possible error in a mail address.
I have no knowledge of the specific gem, but if a regex solution will do, you could add
require 'mail'
mail = Mail.new do
from 'peter#lustig.de'
to ("#{send.toAdress}")
subject ("#{send.subject}")
html_part do
content_type 'text/html; charset=UTF-8'
body ("#{send.body}")
end
end if send.toAdress.match /regex_to_match_email/
As for the /regex_to_match_email/, I think it's a matter of taste. I usually use this one
\b[A-Z0-9._%-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\b
however, it is far from perfect.
We have a function in our Rails code that accepts a JSON POST body:
contacts = ActiveSupport::JSON.decode(request.raw_post.gsub("+", ""))
(I'm aware that I can get this from params["_json"] as well, but we have extremely large (MBs) POST bodies that do not get put into params["_json"] for some reason (and + throws errors too).
Since the JSON is usually sent from a mobile client, it's important to us to optimize the upload size. We want to switch to having the POST body gzipped.
However, no matter what we do, we get the same error with no line number:
MultiJson::DecodeError (743: unexpected token at ''):
We have tried:
gzipped_contacts = Zlib::GzipReader.new(StringIO.new(request.raw_post)).read
contacts = ActiveSupport::JSON.decode(gzipped_contacts.gsub("+", ""))
This:
gzipped_contacts = ActiveSupport::Gzip.decompress(request.raw_post)
contacts = ActiveSupport::JSON.decode(gzipped_contacts.gsub("+", ""))
And the solution found here: Rails: how to unzip a compressed xml request body?
I'm pretty sure this is not occurring at the controller level because I can't log anything there, so it needs to be done in the middleware or at the server (but I can't find anything for Nginx that lets us deflate). Please assist!
Ok, turns out the iPhone client was sending the wrong headers. So the solution for anyone encountering this is to see the advice here:
Rails: how to unzip a compressed xml request body?
And verify that you are sending Content-Type: gzip/json.
I'm trying to write a system which could pull mails from gmail and get the content in Ruby. (using imap or pop)
And as far as I know, there are 'ruby-gmail', 'mail' (the newer version of tmail) and 'action mailer' that might help me to do this.
I'm now trying 'mail' and 'ruby-gmail', and I use the decoded function like this:
gmail.inbox.emails[0].body.decoded
But some mails could be correctly decoded, but some couldn't.
The output of the decoded mail looks like this:
This is MIME multipart 6.
--__=_Part_Boundary_002_310696381.907173471
Content-Type: text/plain;
charset="big5"
Content-Transfer-Encoding: quoted-printable
=AE=BC=A5=BF=A7A=A6n,
.......(some encoded content)
And to some other mails, the Content-Transfer-Encoding are base64.
Is there any better way to correctly decode the mails?
Or I just need to read into the mail, get the encoded part,
and use Base64.decode64 or unpack.("M") to decode the mail?
I don't know about 'gmail' gem, but 'mail' one works pretty well. Something like
require 'mail'
mail = Mail.new(mail_text)
mail.parts[0].body.decoded
should work (use 'n' instead of 0 for other parts)
Also be aware that it could be an attachment, so you'd need mail.attachments[0].decoded