Rails ActionMailer Quoted Printable Encoding breaks the output - ruby-on-rails

I have this html message that I would like to send using MIME format through a rest api call ( using microsoft send email api ).
#message = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><div dir=\"ltr\">Hello <div><br></div><div>This is the first email<br clear=\"all\"><div><div dir=\"ltr\" class=\"gmail_signature\" data-smartmail=\"gmail_signature\"><div dir=\"ltr\"><span><p style=\"color:rgb(34,34,34);line-height:1.2;margin-top:0pt;margin-bottom:0pt\"><br></p><div style=\"color:rgb(34,34,34)\"><br></div><div style=\"color:rgb(34,34,34)\"><font face=\"verdana, sans-serif\">Regards</font></div><p style=\"color:rgb(34,34,34);line-height:1.2;margin-top:0pt;margin-bottom:0pt\">--</p><div style=\"color:rgb(34,34,34)\"><div dir=\"ltr\"><div dir=\"ltr\"><p style=\"line-height:1.2;margin-top:0pt;margin-bottom:0pt\"><font face=\"verdana, sans-serif\"><span style=\"font-size:10pt;color:rgb(11,83,148);vertical-align:baseline;white-space:pre-wrap\">Md. Imam Hossain</span><span style=\"font-size:10pt;color:rgb(11,83,148);vertical-align:baseline;white-space:pre-wrap\">, B.Sc.(Engg.)</span><br></font></p><p style=\"line-height:1.656;margin-top:0pt;margin-bottom:0pt\"><font color=\"#999999\" face=\"verdana, sans-serif\"><span style=\"font-size:13.3333px;white-space:pre-wrap\">Software Engineer</span></font></p><font face=\"verdana, sans-serif\"><span style=\"font-size:10pt;color:rgb(153,153,153);vertical-align:baseline;white-space:pre-wrap\">Mobile: </span><span style=\"font-size:10pt;color:rgb(11,83,148);vertical-align:baseline;white-space:pre-wrap\">+880168</span></font></div><div dir=\"ltr\"><span style=\"color:rgb(11,83,148);vertical-align:baseline;white-space:pre-wrap\"><font style=\"display:inline\" face=\"verdana, sans-serif\"><font color=\"#0b5394\">Linked</font><b><font color=\"#ffffff\" style=\"background-color:rgb(61,133,198)\">in</font></b><font color=\"#0b5394\">profile</font></font><font face=\"Verdana\" style=\"font-size:10pt\"><br></font></span></div></div></div></span></div></div></div></div></div>\r\n\r\n<br>\r\n<div dir=\"ltr\" style=\"background-color:rgb(255,255,255)\"><div dir=\"ltr\"><div dir=\"ltr\"><div dir=\"ltr\"><div dir=\"ltr\" style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;color:rgb(0,0,0)\"></div><p><font color=\"#808080\" face=\"Verdana\" size=\"2\"><img src=\"https://ci4.googleusercontent.com/proxy/e6V3E5U5l5e0xH2os1Sks-xfQjBYMWwJOO5sgcpNB190WDnylH_uKYihXj2O2m1cIBbhstXNdMDcJUY8uAQvnSGQLiLMJENd-NfkIYgAPP31xQWC5bb0EuUKtnkhYhcO-HZOJgHl25DrccD_PK5iCd8CaC7Xxw=s0-d-e1-ft#https://cdn1.pegasaas.io/41fc/img/wp-content/uploads/2020/11/SELISE-DIgital-Platforms----145x60.png\"><br></font></p><p><font color=\"#808080\" face=\"Verdana\" size=\"2\">Secure Link Services Group<br>Zürich: The Circle 37, 8058 Zürich-Airport, Switzerland<br>Munich: Tal 44, 80331 München, Germany<br>Dubai: Building 3, 3rd Floor, Dubai Design District, Dubai, United Arab Emirates<br>Dhaka: Midas Center, Road 16, Dhanmondi, Dhaka 1209, Bangladesh<br>Thimphu: Bhutan Innovation Tech Center, Babesa, P.O. Box 633, Thimphu, Bhutan<br><br>Visit us: www.selise.ch<br></font></p></div></div></div></div>\r\n<br>\r\n<div dir=\"ltr\" style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px\"></div><div dir=\"ltr\" style=\"font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px\"></div><p><i><font size=\"1\"><strong>Important Note:</strong> This e-mail and any attachment are confidential and may contain trade secrets and may well also be legally privileged or otherwise protected from disclosure. If you have received it in error, you are on notice of its status. Please notify us immediately by reply e-mail and then delete this e-mail and any attachment from your system. If you are not the intended recipient please understand that you must not copy this e-mail or any attachment or disclose the contents to any other person. Thank you for your cooperation.</font></i><br></p>"
def send_message(subject:, to_address:, message:)
mail(
to: to_address,
subject:,
body: message,
)
end
My ActionMailer BaseClass looks like:
class ApplicationMailer < ActionMailer::Base
default content_transfer_encoding: '7bit'
layout 'mailer'
end
Now when I do mail.to_s, it encodes the message with Content-Transfer-Encoding: quoted-printable instead of 7bit. However, it encodes the message with 7bit if the #message text is shorter.
Date: Wed, 24 Aug 2022 14:33:56 +0600
From: graph-api-mailer#test.ch
To: ["graph-api-mailer#test.ch"]
Message-ID: <3f8735c7-a2eb-4ca7-a208-2de0145c7b01#selise.ch>
In-Reply-To: <>
References: <>
Subject: How to hack?
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8">=
<div dir=3D"ltr">Hello <div><br></div><div>This is the first email<b=
r clear=3D"all"><div><div dir=3D"ltr" class=3D"gmail_signature" data-smar=
tmail=3D"gmail_signature"><div dir=3D"ltr"><span><p style=3D"color:rgb(34=
,34,34);line-height:1.2;margin-top:0pt;margin-bottom:0pt"><br></p><div st=
yle=3D"color:rgb(34,34,34)"><br></div><div style=3D"color:rgb(34,34,34)">=
<font face=3D"verdana, sans-serif">Regards</font></div><p style=3D"color:=
rgb(34,34,34);line-height:1.2;margin-top:0pt;margin-bottom:0pt">--</p><di=
v style=3D"color:rgb(34,34,34)"><div dir=3D"ltr"><div dir=3D"ltr"><p styl=
e=3D"line-height:1.2;margin-top:0pt;margin-bottom:0pt"><font face=3D"verd=
ana, sans-serif"><span style=3D"font-size:10pt;color:rgb(11,83,148);verti=
cal-align:baseline;white-space:pre-wrap">Md. Imam Hossain</span><span sty=
le=3D"font-size:10pt;color:rgb(11,83,148);vertical-align:baseline;white-s=
pace:pre-wrap">, B.Sc.(Engg.)</span><br></font></p><p style=3D"line-heigh=
t:1.656;margin-top:0pt;margin-bottom:0pt"><font color=3D"#999999" face=3D=
"verdana, sans-serif"><span style=3D"font-size:13.3333px;white-space:pre-=
wrap">Software Engineer</span></font></p><font face=3D"verdana, sans-seri=
f"><span style=3D"font-size:10pt;color:rgb(153,153,153);vertical-align:ba=
seline;white-space:pre-wrap">Mobile: </span><span style=3D"font-size:10pt=
;color:rgb(11,83,148);vertical-align:baseline;white-space:pre-wrap">+8801=
687242792</span></font></div><div dir=3D"ltr"><span style=3D"color:rgb(11=
,83,148);vertical-align:baseline;white-space:pre-wrap"><a href=3D"https:/=
/www.linkedin.com/in/imam07/" style=3D"color:rgb(17,85,204);display:inlin=
e" target=3D"_blank"><font style=3D"display:inline" face=3D"verdana, sans=
-serif"><font color=3D"#0b5394">Linked</font><b><font color=3D"#ffffff" s=
tyle=3D"background-color:rgb(61,133,198)">in</font></b><font color=3D"#0b=
5394">profile</font></font></a><font face=3D"Verdana" style=3D"font-size:=
10pt"><br></font></span></div></div></div></span></div></div></div></div>=
</div>
<br>
<div dir=3D"ltr" style=3D"background-color:rgb(255,255,255)"><div dir=3D"=
ltr"><div dir=3D"ltr"><div dir=3D"ltr"><div dir=3D"ltr" style=3D"font-fam=
ily:Verdana,Arial,Helvetica,sans-serif;font-size:11px;color:rgb(0,0,0)"><=
/div><p><font color=3D"#808080" face=3D"Verdana" size=3D"2"><img src=3D"h=
ttps://ci4.googleusercontent.com/proxy/e6V3E5U5l5e0xH2os1Sks-xfQjBYMWwJOO=
5sgcpNB190WDnylH_uKYihXj2O2m1cIBbhstXNdMDcJUY8uAQvnSGQLiLMJENd-NfkIYgAPP3=
1xQWC5bb0EuUKtnkhYhcO-HZOJgHl25DrccD_PK5iCd8CaC7Xxw=3Ds0-d-e1-ft#https://=
cdn1.pegasaas.io/41fc/img/wp-content/uploads/2020/11/SELISE-DIgital-Platf=
orms----145x60.png"><br></font></p><p><font color=3D"#808080" face=3D"Ver=
dana" size=3D"2">Secure Link Services Group<br>Z=C3=BCrich: The Circ=
le 37, 8058 Z=C3=BCrich-Airport, Switzerland<br>Munich: Tal 44, 8033=
1 M=C3=BCnchen, Germany<br>Dubai: Building 3, 3rd Floor, Dubai Desig=
n District, Dubai, United Arab Emirates<br>Dhaka: Midas Center, Road=
16, Dhanmondi, Dhaka 1209, Bangladesh<br>Thimphu: Bhutan Innovation=
Tech Center, Babesa, P.O. Box 633, Thimphu, Bhutan<br><br>Visit us:&nbsp=
;www.selise.ch<br>=
</font></p></div></div></div></div>
<br>
<div dir=3D"ltr" style=3D"font-family:Verdana,Arial,Helvetica,sans-serif;=
font-size:11px"></div><div dir=3D"ltr" style=3D"font-family:Verdana,Arial=
,Helvetica,sans-serif;font-size:11px"></div><p><i><font size=3D"1"><stron=
g>Important Note:</strong> This e-mail and any attachment are confid=
ential and may contain trade secrets and may well also be legally privile=
ged or otherwise protected from disclosure. If you have received it in er=
ror, you are on notice of its status. Please notify us immediately by rep=
ly e-mail and then delete this e-mail and any attachment from your system=
. If you are not the intended recipient please understand that you must n=
ot copy this e-mail or any attachment or disclose the contents to any oth=
er person. Thank you for your cooperation.</font></i><br></p>=
I send it like this afterwards:
MicrosoftClient::Mail::Send.call(mail: Base64.encode64(mail.to_s)) # own class to send through microsoft graph api
When I send this mail, the received mail does not render correctly in gmail/outlook.
I have tried with Content-Transfer-Encoding: 'binary' , but that doesn't work when I add attachments.
How can I send this email correctly so that encoding is 7bit or the output does not break in the mail clients?

Fixed with
mail(
to: to_address,
subject: subject,
content_type: 'text/html'
) do |format|
format.html(content_transfer_encoding: 'base64') { Base64.encode64(#message) }
end
Here, transfer encoding should be base64, previously was trying with binary

Related

How to add more than 1000 email address using mail_to

Currently i have the below code in index view
<div id = "GetEmails"><%= mail_to "xyz#gmail.com" do %><strong>Send Mail</strong><% end %></div>
In runtime, I am updating the value of mailto: which consists of more than 1000
+email address
Now, when I click on "Send Mail", outlook is not getting opened by copying all the email address in To: field
But, if the number of email address are < 100, the on click of "Send Mail" I am able to copy all the email address in To: filed
Manually, I am able to copy more than 3000 email address.
How do I get copy all the email address in To line by clicking "Send Mail".
The mailto:(as any other URL) has a character limit for the URL, it's vary from browser to browser, or from E-Mail client to E-Mail client.
Please try to use JS.
You could have a HTML element with data attribute where all emails would be stored and then try this:
<div id="my-mails-storing-element-id" data-mails="person1#domain.com, person2#domain.com,person3#domain.com"></div>
var mailsDom = document.getElementById("my-mails-storing-element-id")
location.href = mailto:mailsDom.dataset.mails;
But it would be better if you create separate action in your controller to get those emails. Then you just need to make AJAX call to get mails data.
I hope it'd help you. Please let me now if it worked (I've tried with Mailspring mail client and it worked)

microsoft graph createreply wrong timezone

In our Angular 5 application, we are using Microsoft Graph to retrieve mail messages from a mailbox and then send a reply. For creating the reply, we use the REST API:
https://graph.microsoft.com/v1.0/me/messages/{message ID}/createReply
This creates a reply with a timezone that is UTC, but we expect it to be GMT+1. For example, in the mail body it says:
From: Melissa van Dijk
Sent: Friday, February 23, 2018 9:51:49 AM (wrong timestamp)
To: Melissa van Dijk
Subject: Meet for lunch?
We checked the settings in our Office 365 mail account and there it is specified that our local timezone is GMT+1 (Brussels, Amsterdam...).
(When replying via Outlook webmail, we get a correct timestamp).
Is this a bug or do we have to correct this timestamp ourselves? Or do we need to pass the timezone with the REST call somehow?
Thanks in advance!
I'm not sure if you can adjust the human-formatted timestamps in the HTML message body, but you can certainly use the ISO 8601 formatted timestamps in the other fields in the JSON of the response. For example, you'll find:
{
...
"sentDateTime": "2018-02-23T09:51:49Z",
...
}
While this is also in UTC (denoted by the Z), you can easily parse it by using a JavaScript Date object, or Angular's own datetime functions, or your favorite time library such as Luxon, Moment, or Date-fns. From there, displaying it in local time is trivial.

Mandrill embed image cannot get interpreted properly

I'm using Mandrill in Nodejs to send emails to customers, and want to embed an image in my html content attached to the emails. I found some solutions from https://mandrill.zendesk.com/hc/en-us/articles/205582457-Tips-for-using-images-in-Mandrill-emails and decided to use the 4th one, which includes the image inline in the html. The code is as below:
var message = {
html: htmlContent,
subject: "Subject",
...
images = [{
"type": logo.logoType, // which is "image/jpeg"
"name": "logo",
"content": content // the content is valid when using in <img src="data:image/jpeg;base64,content"> directly
}]
};
mandrillClient.messages.send({
message: message
}, function() {
callback(null);
}, function(err) {
callback(err);
})
And the corresponding html code in htmlContent is
<img src="cid:logo">
However, when I checked with the sent email in Mandrill Outbound Activity, "View Content" doesn't show the image correctly. And I looked at the html source, found the image code was still as
<img src="cid:logo">
The plain text didn't get replaced by image data.
How can I get it work properly? Any ideas?
Thanks in advance.
Finally I found it just didn't work when checking the sent email in Mandrill Outbound Activity, but it works fine in your actual email inbox. So it means you will never see the embedded image when do testing, you must send it with production api key and see the results in a real email inbox.
Just FYI. Hope no one will waste any time on this like I did. :)

How to get RSVP buttons through Icalendar gem

So I've got the Icalendar gem in my ruby project. I'm attempting to get the RSVP buttons of Yes/No/Maybe on the invite but whenever it gets sent I only get a "Add to Calendar".
Was wondering what else I need:
def make_ical_appointment(start_time, end_time, uid, email_one, email_two)
ical = Icalendar::Calendar.new
ical.timezone.tzid = "UTC"
e = Icalendar::Event.new
e.dtstart = start_time
e.dtend = end_time
e.organizer = %W(mailto:#{email_one} mailto#{email_two})
e.uid = uid
ical.add_event(e)
ical.publish
mail.attachments['appointment.ics'] = { mime_type: 'application/ics', content: ical.to_ical }
end
I've read that people need to set it to METHOD:REQUEST, but I'm not sure where to do there. I've also read that you need to set attendees, but it seems you can only set attendees if you have an alarm?
Just looking to get it to look like a regular invite.
There's two things you need to do to solve your problem:
Read RFC-2445, which defines the iCal format. It looks like section 4.8.4.1, which discusses the ATTENDEE property, and 4.2.17, which discusses the RSVP parameter, will be of particular interest.
Look at emails and .ics files you've received that display correctly in various email clients.
The page I linked to in my comment above has three hints.
The first hint
I tried adding this property:calendar.custom_property("METHOD", "REQUEST").[1]
From the docs I think that's supposed to be append_custom_property.
Opening up an invite someone sent me from Google calendar, I found this line:
METHOD:REQUEST
So that seems legit.
The second hint
I would guess that you need to add an ATTENDEE property with RSVP=TRUE and the email set to the same email that Outlook or Yahoo link to their users.[2]
In the same invite I found this:
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=
TRUE;CN=Firstname Lastname;X-NUM-GUESTS=0:mailto:jordan#example.com
I didn't read the whole RFC, but I think it breaks down like this:
ATTENDEE is the property name.
Everything between the first ; and the first : are parameters. Each of them are documented in the RFC, and I don't know if all of them are required, but we can see the RSVP=TRUE parameter there.
Everything after the first :, i.e. mailto:jordan#example.com is the value
Looking at the source of append_custom_property we see that it checks if value is an Icalendar::Value object, and if not it creates one with Icalendar::Values::Text.new(value). Since we have parameters in addition to a value, let's check out that constructor here. We see that it can take a second argument, which is a params Hash.
Now, I haven't tested it, but that suggests to me that you can build a line like the above with code something like the following†:
attendee_params = { "CUTYPE" => "INDIVIDUAL",
"ROLE" => "REQ-PARTICIPANT",
"PARTSTAT" => "NEEDS-ACTION",
"RSVP" => "TRUE",
"CN" => "Firstname Lastname",
"X-NUM-GUESTS" => "0" }
attendee_value = Icalendar::Values::Text.new("MAILTO:jordan#example.com", attendee_params)
ical.append_custom_property("ATTENDEE", attendee_value)
Edit: In Icalendar 2.x it looks like you can also do:
attendee_value = Icalendar::Values::CalAddress.new("MAILTO:jordan#example.com", attendee_params)
ical.append_attendee(attendee_value)
The CalAddress class is a subclass of Uri, which just runs the given value through URI.parse, and append_attendee appears to be a shortcut for append_custom_property("ATTENDEE", ...).
I'm not sure if all of those parameters are actually required, but you can learn what each of them is by reading the RFC.
The third hint
What I had to do to make it work in all mail clients was to send it as a multipart/alternative message with the ical as an alternative view instead of as an attachment.[3]
Sure enough, doing "Show Original" in Gmail I saw that the invite email I got is a multipart email, with a text/calendar part:
--047d7b0721581f7baa050a6c3dc0
Content-Type: text/calendar; charset=UTF-8; method=REQUEST
Content-Transfer-Encoding: 7bit
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
...
...and an application/ics attachment part:
--047d7b0721581f7bae050a6c3dc2
Content-Type: application/ics; name="invite.ics"
Content-Disposition: attachment; filename="invite.ics"
Content-Transfer-Encoding: base64
QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vR29vZ2xlIEluYy8vR29vZ2xlIENhbGVuZGFyIDcw
...
The second part you've already got, thanks to mail.attachments. For the first part, you just have to create a new Mail::Part with the correct content_type and add it to mail.parts, which will look something like this:
ical_part = Mail::Part.new do
content_type "text/calendar; charset=UTF-8; method=REQUEST"
body ical.to_ical
end
mail.add_part(ical_part)
That's all I've got. Again, I've tested none of this, and I'm not certain it'll fix your problem, but hopefully it gives you a few ideas.
The most important thing, I think, is to look at the source of emails (if you use Gmail, "Show Original" is under the drop-down menu next to the Reply button) with invites and look at how they're constructed, and likewise look at the .ics attachments and see whether or not they match what you're generating.
Good luck!
†Judging by the way Icalendar transforms the params hash into iCal parameters, I think you can use symbol keys, too, like so:
attendee_params = { cutype: "INDIVIDUAL",
role: "REQ-PARTICIPANT",
partstat: "NEEDS-ACTION",
rsvp: "TRUE",
cn: "Firstname Lastname",
x_num_guests: "0" }

While processing an email reply how can I ignore any email client specifics & the history?

I have a rails application which processes incoming emails via IMAP. Currently a method is used that searches the parts of a TMail object for a given content_type:
def self.search_parts_for_content_type(parts, content_type = 'text/html')
parts.each do |part|
if part.content_type == content_type
return part.body
else
if part.multipart?
if body = self.search_parts_for_content_type(part.parts, content_type)
return body
end
end
end
end
return false
end
These emails are generally in response to a html email it sent out in the first place. (The original outbound email is never the same.) The body text the method above returns contains the full history of the email and I would like to just parse out the reply text.
I'm wondering whether it's reasonable to place some '---please reply above this line---' text at the top of the mail as I have seen in a 37 signals application.
Is there another way to ignore the client specific additions to the email, other than write a multitude of regular expressions (which I haven't yet attempted) for each and every mail client? They all seem to tack on their own bit at the top of any replies.
I have to do email reply parsing on a project I'm working on right now. I ended up using pattern matching to identify the response part, so users wouldn't have to worry about where to insert their reply.
The good news is that the implementation really isn't too difficult. The hard part is just testing all the different email clients and services you want to support and figuring out how to identify each one. Generally, you can use either the message ID or the X-Mailer or Return-Path header to determine where an incoming email came from.
Here's a method that takes a TMail object and extracts the response part of the message and returns that along with the email client/service it was sent from. It assumes you have the original message's From: name and address in the constants FROM_NAME and FROM_ADDRESS.
def find_reply(email)
message_id = email.message_id('')
x_mailer = email.header_string('x-mailer')
# For optimization, this list could be sorted from most popular to least popular email client/service
rules = [
[ 'Gmail', lambda { message_id =~ /.+gmail\.com>\z/}, /^.*#{FROM_NAME}\s+<#{FROM_ADDRESS}>\s*wrote:.*$/ ],
[ 'Yahoo! Mail', lambda { message_id =~ /.+yahoo\.com>\z/}, /^_+\nFrom: #{FROM_NAME} <#{FROM_ADDRESS}>$/ ],
[ 'Microsoft Live Mail/Hotmail', lambda { email.header_string('return-path') =~ /<.+#(hotmail|live).com>/}, /^Date:.+\nSubject:.+\nFrom: #{FROM_ADDRESS}$/ ],
[ 'Outlook Express', lambda { x_mailer =~ /Microsoft Outlook Express/ }, /^----- Original Message -----$/ ],
[ 'Outlook', lambda { x_mailer =~ /Microsoft Office Outlook/ }, /^\s*_+\s*\nFrom: #{FROM_NAME}.*$/ ],
# TODO: other email clients/services
# Generic fallback
[ nil, lambda { true }, /^.*#{FROM_ADDRESS}.*$/ ]
]
# Default to using the whole body as the reply (maybe the user deleted the original message when they replied?)
notes = email.body
source = nil
# Try to detect which email service/client sent this message
rules.find do |r|
if r[1].call
# Try to extract the reply. If we find it, save it and cancel the search.
reply_match = email.body.match(r[2])
if reply_match
notes = email.body[0, reply_match.begin(0)]
source = r[0]
next true
end
end
end
[notes.strip, source]
end
I think you will be stuck on this one. I have been doing some stuff with emails myself in TMail recently, and what you will generally find is that an email that has an HTML part is generally structured like:
part 1 - multipart/mixed
sub part 1 - text/plain
sub part 2 - text/html
end
The email clients I have played with Outlook and Gmail both generate replies in this format, and they just generally quote the original email inline in the reply. At first I though that the 'old' parts of the original email would be separate parts, but they are actually not - the old part is just merged into the reply part.
You could search the part for a line that begins 'From: ' (as most clients generally place a header at the top of the original email text detailing who sent it etc), but its probably not guaranteed.
I don't really see anything wrong with a --- please reply above this line --- generally, its not that invasive, and could make things a lot simpler.

Resources