ruby mail gem - set charset in deliver block - ruby

I'm using the mail gem to send E-Mail with UTF-8 content using this code
Mail.defaults do
...
end
Mail.deliver do
from "user#example.com"
to "otheruser#example.com"
subject "Mäbülö..."
body "Märchenbücher lösen Leseschwächen."
end
This works, but gives the warning
Non US-ASCII detected and no charset defined.
Defaulting to UTF-8, set your own if this is incorrect.
Now after much trying around, consulting mail gem's generated documentation as well as source code, I'm still unable to set the charset. There is a method charset= in Message.rb, but when I add a call to charset, like so:
Mail.deliver do
from "user#example.com"
to "otheruser#example.com"
charset "UTF-8"
subject "Mäbülö..."
body "Märchenbücher lösen Leseschwächen."
end
I get this ArgumentError:
/usr/local/lib/ruby/gems/1.9.1/gems/mail-2.4.4/lib/mail/message.rb:1423:in `charset': wrong number of arguments (1 for 0) (ArgumentError)
How can I set the charset within the deliver block?

mail.charset() returns the current charset, it does not allow to set one and does not take any argument.
To do so you need to use mail.charset = ...
It's actually possible inside the block with:
Mail.deliver do
from "user#example.com"
to "otheruser#example.com"
subject "Mäbülö..."
body "Märchenbücher lösen Leseschwächen."
charset = "UTF-8"
end
It's also possible without a block:
mail = Mail.new
mail.charset = 'UTF-8'
mail.content_transfer_encoding = '8bit'
mail.from = ...
mail.to = ...
mail.subject = ...
mail.text_part do
body ...
end
mail.html_part do
content_type 'text/html; charset=UTF-8'
body ...
end
mail.deliver!

you need to set the encoding also for the individual parts. Answer by maxdec shows it. Ensure that you do this for the text_part also.
This works for me.
mail = Mail.deliver do
charset='UTF-8'
content_transfer_encoding="8bit"
require 'pry';binding.pry
to 'xxx#xxx.yy'
from 'yyy#yyy.ss'
subject "Tet with äöüß"
text_part do
content_type "text/plain; charset=utf-8"
body <<-EOF
this is a test with äöüß
EOF
end
end
mail.deliver!

I use mail (2.7.1), neither charset nor content_transfer_encoding work for me.
charset='UTF-8'
content_transfer_encoding="8bit"
The following works for me!
content_type "text/plain; charset=utf-8"

Related

Send accentuated characters inside an email body, how to fix encoding issues

In a script which sends emails through Net::SMTP, I've to figure out how to properly encode the email body in order to support accentuated characters. I've separated the email into 3 parts: headers, body and attachment, as from this tutorial https://www.tutorialspoint.com/ruby/ruby_sending_email.htm
Fixing this issue for the Subject header field wasn't a big deal:
require 'base64'
MARKER = 'FOOBAR'
def self.headers
<<~EOF
From: someemail#domain.tld
To: anotheremail#domaim.tld
# Base64 encoded UTF-8
Subject: =?UTF-8?B?#{Base64.strict_encode64('Accentuated characters supportés')}?=
Date: #{Time.now.to_s}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{MARKER}
--#{MARKER}
EOF
end
I was tempted to reproduce the same logic for the email body, without any success. I've try several headers, such as Language, Content-Language, Content-Tranfer-Encoding, again, without any success. Using Ruby's .encode!('utf-8') was also ineffective.
The only working solution I can think of would be to send HTML encoded characters: using &eacute instead of é inside a HTML block. Though, I'd like to avoid this solution as I've to improve my comprehension of encoding issues.
Does anyone has a suggestion about this issue ?
Here's my code so far, if it can help anyone:
module Reports
module SMTP
MARKER = 'FOOBAR'
def self.send_report(file_path)
file_content = File.binread(file_path)
encoded_content = [file_content].pack('m') # Base64
mail_content = headers + body + attachment(file_path, encoded_content)
begin
Net::SMTP.start('my.smtp.srv', 25, 'HELO_fqdn', 'username', 'p455w0rD', :plain) do |smtp|
smtp.send_message(mail_content, 'from#domain.tld', ['to1#domain.tld', 'to2#domain.tld'])
end
rescue => e
puts e.inspect, e.backtrace
end
end
def self.headers
# see above
end
def self.body
<<~EOF
Content-Type: text/html
Content-Transfer-Encoding:utf8
Here's a franglish text with some characters accentués
--#{MARKER}
EOF
end
def self.attachment(file_path, encoded_content)
<<~EOF
Content-Type: multipart/mixed; name = #{file_path}
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename = #{file_path}
#{encoded_content}
--#{MARKER}--
EOF
end
end
end
Note: these emails are correctly decoded by ProtonMail webclients, but our company's webclient (OBM) doesn't display accentuated character nor attachment properly.
Adding ;charset="utf-8" to the Content-Type header from the body part fixed my problem.
Content-Type: text/html;charset="utf-8"

incompatible character encodings: ASCII-8BIT and UTF-8 in Oga gem

I am using an XML/HTML parser called Oga.
I am attempting to crawl this URL: http://www.johnvanderlyn.com and parse the body for text, like so:
def get_page
body = Net::HTTP.get(URI.parse(#url))
document = Oga.parse_html(body)
end
document = get_page
words = document.css('body').text
When I get this error:
/gems/oga-2.7/lib/oga/xml/node_set.rb:276:in block in text': incompatible character encodings: ASCII-8BIT and UTF-8 (Encoding::CompatibilityError)
That is related to this bit of code here.
What could be causing this and how can I fix it? Is there a way for me to fix it locally, or do I have to fork the gem, fix that method and then use my fork?
Thoughts?
The bit of code you linked has nothing to do with the glitch, that is the issue of body is being interpreted in wrong encoding. Try adding body = body.force_encoding 'UTF-8' before parsing a document:
def get_page
body = Net::HTTP.get(URI.parse(#url)).force_encoding 'UTF-8'
document = Oga.parse_html(body)
end

Reading a Gmail Message with ruby-gmail

I am looking for an instance method from the ruby-gmail gem that would allow me to read either:
the body
or
subject
of a Gmail message.
After reviewing the documentation, found here, I couldn't find anything!?
There is a .message instance method found in the Gmail::Message class section; but it only returns, for lack of a better term, email "mumbo-jumbo," for the body.
My attempt:
#!/usr/local/bin/ruby
require 'gmail'
gmail = Gmail.connect('username', 'password')
emails = gmail.inbox.emails(:from => 'someone#mail.com')
emails.each do |email|
email.read
email.message
end
Now:
email.read does not work
email.message returns that, "mumbo-jumbo," mentioned above
Somebody else asked this question on SO but didn't get an answer.
This probably isn't exactly the answer to your question, but I will tell you what I have done in the past. I tried using the ruby-gmail gem but it didn't do what I wanted it to do in terms of reading a message. Or, at least, I couldn't get it to work. Instead I use the built-in Net::IMAP class to log in and get a message.
require 'net/imap'
imap = Net::IMAP.new('imap.gmail.com',993,true)
imap.login('<username>','<password>')
imap.select('INBOX')
subject_id = search_mail(imap, 'SUBJECT', '<mail_subject>')
subject_message = imap.fetch(subject_id,'RFC822')[0].attr['RFC822']
mail = Mail.read_from_string subject_message
body_message = mail.html_part.body
From here your message is stored in body_message and is HTML. If you want the entire email body you will probably need to learn how to use Nokogiri to parse it. If you just want a small bit of the message where you know some of the surrounding characters you can use a regex to find the part you are interested in.
I did find one page associated with the ruby-gmail gem that talks about using ruby-gmail to read a Gmail message. I made a cursory attempt at testing it tonight but apparently Google upped the security on my account and I couldn't get in using irb without tinkering with my Gmail configuration (according to the warning email I received). So I was unable to verify what is stated on that page, but as I mentioned my past attempts were unfruitful whereas Net::IMAP works for me.
EDIT:
I found this, which is pretty cool. You will need to add in
require 'cgi'
to your class.
I was able to implement it in this way. After I have my body_message, call the html2text method from that linked page (which I modified slightly and included below since you have to convert body_message to a string):
plain_text = html2text(body_message)
puts plain_text #Prints nicely formatted plain text to the terminal
Here is the slightly modified method:
def html2text(html)
text = html.to_s.
gsub(/( |\n|\s)+/im, ' ').squeeze(' ').strip.
gsub(/<([^\s]+)[^>]*(src|href)=\s*(.?)([^>\s]*)\3[^>]*>\4<\/\1>/i,
'\4')
links = []
linkregex = /<[^>]*(src|href)=\s*(.?)([^>\s]*)\2[^>]*>\s*/i
while linkregex.match(text)
links << $~[3]
text.sub!(linkregex, "[#{links.size}]")
end
text = CGI.unescapeHTML(
text.
gsub(/<(script|style)[^>]*>.*<\/\1>/im, '').
gsub(/<!--.*-->/m, '').
gsub(/<hr(| [^>]*)>/i, "___\n").
gsub(/<li(| [^>]*)>/i, "\n* ").
gsub(/<blockquote(| [^>]*)>/i, '> ').
gsub(/<(br)(| [^>]*)>/i, "\n").
gsub(/<(\/h[\d]+|p)(| [^>]*)>/i, "\n\n").
gsub(/<[^>]*>/, '')
).lstrip.gsub(/\n[ ]+/, "\n") + "\n"
for i in (0...links.size).to_a
text = text + "\n [#{i+1}] <#{CGI.unescapeHTML(links[i])}>" unless
links[i].nil?
end
links = nil
text
end
You also mentioned in your original question that you got mumbo-jumbo with this step:
email.message *returns mumbo-jumbo*
If the mumbo-jumbo is HTML, you can probably just use your existing code with this html2text method instead of switching over to Net::IMAP as I had discussed when I posted my original answer.
Nevermind, it's:
email.subject
email.body
silly me
ok, so how do I get the body in "readable" text? without all the encoding stuff and html?
Subject, text body and HTML body:
email.subject
if email.message.multipart?
text_body = email.message.text_part.body.decoded
html_body = email.message.html_part.body.decoded
else
# Only multipart messages contain a HTML body
text_body = email.message.body.decoded
html_body = text
end
Attachments:
email.message.attachments.each do |attachment|
path = "/tmp/#{attachment.filename}"
File.write(path, attachment.decoded)
# The MIME type might be useful
content_type = attachment.mime_type
end
require 'gmail'
gmail = Gmail.connect('username', 'password')
emails = gmail.inbox.emails(:from => 'someone#mail.com')
emails.each do |email|
puts email.subject
puts email.text_part.body.decoded
end

How to pass variables into Mail body template?

I am trying to write simple mailer in Sinatra which sends email with params variables.
require 'sinatra'
require 'mail'
class App < Sinatra::Base
post '/test_mailer' do
company = params['Field6']
email = params['Field5']
puts "Company name: #{company}"
puts "Email: #{email}"
mail = Mail.new do
from 'me#mydomain.com'
to 'me#mydomain.com'
subject 'Here is the image you wanted'
text_part do
body "Company Name \n === \n #{company} \n \n Email \n === \n #{email}"
end
end
mail.deliver!
end
end
How to move email template to test_mailer.txt with company and email variables ?
I'm not sure I understand you - you want an separate email template file, right? I'm thinking you could use an erb, or haml template and then do something like the following:
text_part do
body erb(:test_mailer)
end
Your test_mailer.erb file would then contain your email template.
Here shows how something similar is done using pony.

Mail gem determine whether plaintext or html

I'm using the mail gem from https://github.com/mikel/mail
I use it to parse raw mail data: e.g
require 'mail'
maildata = Mail.new(body) #where body is the raw text of an email message
#from there I can see info such as
p maildata.body.decoded #displays the decoded email body
p maildata.from #shows who the email is from
How would I figure out whether the email is plaintext or html is there a built in way to do this?
You could look at maildata.content_type:
maildata.content_type
#=> "text/plain; charset=us-ascii"
If it's a multipart e-mail, you could have both plain text and HTML. You could then look at the parts array to see which content types it includes:
maildata.content_type
#=> "multipart/alternative; boundary=\"--==_mimepart_4f848491e618f_7e4b6c1f3849940\"; charset=utf-8"
maildata.parts.collect { |part| part.content_type }
#=> ["text/plain; charset=utf-8", "text/html; charset=utf-8"]
mail = Mail.new do
text_part do
body 'test'
end
html_part do
content_type 'text/html; charset=utf-8'
body '<b>test</b>'
end
end
mail.multipart? # true
puts mail.html_part.decoded # "<b>test</b>"
puts mail.text_part # "test"

Resources