Sending an e-mail with an attachment using ruby net/smtp - ruby

I've been trying to figure out how to send an e-mail to my gmail account with a binary attachment using the standard net/smtp. So far, I've succeeded in attaching a text file successfully - the following (based on what others have done) works for this:
#!/usr/bin/env ruby
require 'net/smtp'
addressee = 'NAME#EMAIL.COM'
server = 'smtp.gmail.com'
port = 587
account = 'ACCOUNT'
from = addressee
name = 'NAME'
domain = 'gmail.com'
subject = 'test of smtp using ruby'
body = 'Test of SMTP using Ruby.'
marker = "PART_SEPARATOR"
filename = "test-attachment"
filetext = "attachment contents"
print "Enter password for #{account}: "
password = $stdin.gets.chomp
# Define the main headers.
part1 = <<EOF
From: #{name} <#{from}>
To: <#{addressee}>
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{marker}
--#{marker}
EOF
# Define the message action
part2 = <<EOF
Content-Type: text/plain
Content-Transfer-Encoding:8bit
#{body}
--#{marker}
EOF
# Define the attachment section
part3 = <<EOF
Content-Type: text/plain
Content-Disposition: attachment; filename="#{File.basename(filename)}"
#{filetext}
--#{marker}--
EOF
message = part1 + part2 + part3
puts message
smtp = Net::SMTP.new server, port
smtp.enable_starttls
smtp.start(domain, account, password, :login) do
smtp.send_message message, from, addressee
end
The problem is replacing a text attachment with an encoded binary attachment. The following variation of the above looks like it should work based on what I've been able to google, but does not send the attachment correctly:
#!/usr/bin/env ruby
require 'net/smtp'
addressee = 'NAME#EMAIL.COM'
server = 'smtp.gmail.com'
port = 587
account = 'ACCOUNT'
from = addressee
name = 'NAME'
domain = 'gmail.com'
subject = 'test of smtp using ruby'
body = 'Test of SMTP using Ruby.'
marker = "PART_SEPARATOR"
filename = "test-attachment"
filetext = "attachment contents"
print "Enter password for #{account}: "
password = $stdin.gets.chomp
# Encode contents into base64 format
encodedcontent = [filetext].pack("m")
# Define the main headers.
part1 = <<EOF
From: #{name} <#{from}>
To: <#{addressee}>
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=#{marker}
--#{marker}
EOF
# Define the message action
part2 = <<EOF
Content-Type: text/plain
Content-Transfer-Encoding:8bit
#{body}
--#{marker}
EOF
# Define the attachment section
part3 = <<EOF
Content-Type: multipart/mixed; name="#{File.basename(filename)}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename="#{File.basename(filename)}"
#{encodedcontent}
--#{marker}--
EOF
message = part1 + part2 + part3
puts message
smtp = Net::SMTP.new server, port
smtp.enable_starttls
smtp.start(domain, account, password, :login) do
smtp.send_message message, from, addressee
end
Can anyone tell me what I'm doing wrong?

I've finally managed to send a binary attachment - the secret was using
Content-Type: application/octet-stream; name="#{filename}"
Content-Disposition: attachment; filename="#{filename}"; size=#{size}
in the attachment portion (part 3) of the message.
One other thing, this worked fine for a small test attachment, but when I tried a larger (140K) attachment, the attachment was truncated. Using
filecontent = File.binread(pathname)
rather than
filecontent = File.read(pathname)
seems to solve the problem. (I'm not quite sure why.)

Related

Unable to send Gmail Message with Gmail API

Using the Gmail Service to send an email, but I'm having problem with the email format which needs to be passed to Google::Apis::GmailV1::Message, I'm passing raw parameter to it in the following format
email_raw = "From: <#{#google_account}>
To: <#{send_to}>
Subject: This is the email subject
The email body text goes here"
# raw is: The entire email message in an RFC 2822 formatted and base64url encoded string.
message_to_send = Google::Apis::GmailV1::Message.new(raw: Base64.encode64(email_raw))
response = #service.send_user_message("me", message_to_send)
This fails even when I pass email_raw without base64 encoding. I'm providing valid emails but it fails with an error
Google::Apis::ClientError (invalidArgument: Recipient address required)
I've checked Sending an email with ruby gmail api v0.9 and I also found this but it uses Mail class which I could not locate in the Gmail API Ruby client library. Currently, email_raw contains \n characters but I've tested it without it and it doesn't work.
Moreover, I also want to send attachments in a message.
We can easily offload the effort of forming a standardized and formatted email to this gem. Just include the gem in your project and do this
mail = Mail.new
mail.subject = "This is the subject"
mail.to = "someperson#gmail.com"
# to add your html and plain text content, do this
mail.part content_type: 'multipart/alternative' do |part|
part.html_part = Mail::Part.new(body: email_body, content_type: 'text/html')
part.text_part = Mail::Part.new(body: email_body)
end
# to add an attachment, do this
mail.add_file(params["file"].tempfile.path)
# when you do mail.to_s it forms a raw email text string which you can supply to the raw argument of Message object
message_to_send = Google::Apis::GmailV1::Message.new(raw: mail.to_s)
# #service is an instance of Google::Apis::GmailV1::GmailService
response = #service.send_user_message("me", message_to_send)
Mind that Gmail requires base64url encoding, not base64 encoding
See documentation:
raw string (bytes format)
The entire email message in an RFC 2822 formatted and base64url encoded string. Returned in messages.get and drafts.get responses when the format=RAW parameter is supplied.
A base64-encoded string.
I recommend you to test first with the Try this API - you can encode the message with online base64url encoders.
Then, when using Ruby, you can use the method:
Base64.urlsafe_encode64(message).
UPDATE
The problem seems to be your raw message body.
The message body should have the followind structure:
To: masroorh7#gmail.com Content-Type: multipart/alternative; boundary="000000000000f1f8eb05b18e8970" --000000000000f1f8eb05b18e8970 Content-Type: text/plain; charset="UTF-8" This is a test email --000000000000f1f8eb05b18e8970 Content-Type: text/html; charset="UTF-8" <div dir="ltr">This is a test email</div> --000000000000f1f8eb05b18e8970--
base64url encoded, this will look like:
encodedMessage = "VG86IG1hc3Jvb3JoN0BnbWFpbC5jb20NCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L2FsdGVybmF0aXZlOyBib3VuZGFyeT0iMDAwMDAwMDAwMDAwZjFmOGViMDViMThlODk3MCINCg0KLS0wMDAwMDAwMDAwMDBmMWY4ZWIwNWIxOGU4OTcwDQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9IlVURi04Ig0KDQpUaGlzIGlzIGEgdGVzdCBlbWFpbA0KDQotLTAwMDAwMDAwMDAwMGYxZjhlYjA1YjE4ZTg5NzANCkNvbnRlbnQtVHlwZTogdGV4dC9odG1sOyBjaGFyc2V0PSJVVEYtOCINCg0KPGRpdiBkaXI9Imx0ciI-VGhpcyBpcyBhIHRlc3QgZW1haWw8L2Rpdj4NCg0KLS0wMDAwMDAwMDAwMDBmMWY4ZWIwNWIxOGU4OTcwLS0"
Thus, your message body should be:
Google::Apis::GmailV1::Message.new(raw:encodedMessage)

Suppressing MimeText Header

Noob, trying to automate sending infrequent personalized emails to a few dozen people, using Python and Thunderbird (I'm fine if I have to click send on each one). Unfortunately, I end up with the following extra header in the body of each message, which I would like to suppress:
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
My code is below:
def send_email(name, email_address):
os.system("thunderbird -compose to= 'to',subject='subject',body='body'")
tbirdPath = r'c:\Program Files (x86)\Mozilla Thunderbird\thunderbird.exe'
msg = MIMEMultipart()
msg['To'] = email_address
msg['Subject'] = 'Hello Friend'
# Body of Email - part of code with display problem (in Thunderbird)
body = "Dear {}".format(name) + '\n' + '\n' + "This is the body." + '\n' + '\n' + "The End." + '\n'
composeCommand = 'format=html,to={},subject={},body={}'.format(msg['To'], msg['Subject'], MIMEText(body))
subprocess.Popen([tbirdPath, '-compose', composeCommand])
When I print to the console, I get the body I expect, but get the extra lines/header when Thunderbird populates. From the first article below, it looks like get_payload can address this (but I have no idea how). Also, I moved to MIME after having no success otherwise, and suspect that is the source of my header. (String Formatting in Python/Thunderbird). Simple answers I can implement are preferred.
Strange unwanted header when reading text file with MIMEText
Python-Parse email Body and truncate MIME headers
You can just truncate as many chars as necessary at the start of body to get rid of the header, i.e.
body = body[96:]
composeCommand = "format=html,to={},subject={},body='{}'".format(msg['To'], msg['Subject'], body)

Unknown algorithm MD5 using net-http-digest_auth

I'm trying to do some digest authorization to a server, then parse the resulting HTML with nokogiri. I'm using the net-http-digest_auth gem (https://github.com/drbrain/net-http-digest_auth) to do the url connection. All is fine up until I start the digest_auth code (line 20); it throws an 'unknown algorithm ""MD5"" error'..
The full error message from the console:
~/.rvm/gems/ruby-1.9.3-p194#rails32/gems/net-http-digest_auth-1.2.1/lib/net/http/digest_auth.rb:105:in 'auth_header': unknown algorithm ""MD5"" (Net::HTTP::DigestAuth::Error)
from ./server_connection.rb:20:in '<main>'
Line 20 is the auth line:
auth = digest_auth.auth_header uri, res['www-authenticate'], 'GET'
Here's my complete code (almost completely verbatim from the sample code used at the github link):
#!/usr/bin/env ruby
require 'uri'
require 'net/http'
require 'net/http/digest_auth'
digest_auth = Net::HTTP::DigestAuth.new
uri = URI.parse 'http://url/controlpage?name=_internal_variables_&asList=1&useJS=True'
uri.user = 'username'
uri.password = 'password'
h = Net::HTTP.new uri.host, uri.port
req = Net::HTTP::Get.new uri.request_uri
res = h.request req
# res is a 401 response with a WWW-Authenticate header
auth = digest_auth.auth_header uri, res['www-authenticate'], 'GET'
# create a new request with the Authorization header
req = Net::HTTP::Get.new uri.request_uri
req.add_field 'Authorization', auth
# re-issue request with Authorization
res = h.request req
if res.code == "200"
page = Nokogiri::HTML(res)
isDaylight = page.css('.controlTitle:contains("isDaylight") ~ .controlValue');
puts isDaylight.content
end
Updated this question to include the request headers via Chrome's dev tools:
GET /_getupdatedcontrols?name=_internal_variables_&asList=True&folderFilter=0&changeCount=479&serverState=idle HTTP/1.1
Host: url
Connection: keep-alive
Cache-Control: no-cache
Authorization: Digest username="username", realm="Indigo Control Server", nonce="71079e9f29f7210325ae451d0f423f07", uri="/_getupdatedcontrols?name=_internal_variables_&asList=True&folderFilter=0&changeCount=479&serverState=idle", algorithm=MD5, response="bc056cc472d35f7967973cb51c5b1a65", qop=auth, nc=00005649, cnonce="18dfcf3e4a7b809d"
X-Indigo-Web-Server-Version: 1
X-Prototype-Version: 1.6.0.3
X-Requested-With: XMLHttpRequest
Pragma: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.17 Safari/536.11
Accept: text/javascript, text/html, application/xml, text/xml, */*
Referer: http://url/controlpage?name=_internal_variables_&asList=1&useJS=True
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
I ended up using the httpclient gem to accomplish the same thing.
The final code to do exactly what i was after:
#!/usr/bin/env ruby
require 'httpclient'
require 'nokogiri'
c = HTTPClient.new
c.debug_dev = STDOUT
c.set_auth("http://domain.com", "username", "password")
doc = Nokogiri::HTML(c.get_content("http://domain.com"))
isDaylight = "";
doc.css('.controlTitle:contains("isDaylight") ~ .controlValue').each do |var|
isDaylight = var.content
end
if (!isDaylight)
system("curl -X PUT --digest -u username:password -d isOn=1 http://domain.com")
else
system("curl -X PUT --digest -u username:password -d isOn=0 http://domain.com")
end
I hope this helps others that may be working with a home automation server and needing to easily do digest-based authentication.
Seth,
I ran into this same issue while working on a script in ruby. I am new to ruby but after a few google searches and some Charles Proxy showing me what was going on, I see that it is common for HTTP implementations to include quotes in the algorithm="MD5" portion of the Auth header, which is incorrect according to spec (it should be algorithm=MD5, with out quotes). Your updated header logs from Chrome devtools shows that your server response is honoring the spec, but the ruby library is NOT when it interprets that response string. This can be seen by
You server's 401 response included:
note the algorithm=MD5
Authorization: Digest username="username", realm="Indigo Control Server", nonce="71079e9f29f7210325ae451d0f423f07", uri="/_getupdatedcontrols?name=_internal_variables_&asList=True&folderFilter=0&changeCount=479&serverState=idle", algorithm=MD5, response="bc056cc472d35f7967973cb51c5b1a65", qop=auth, nc=00005649, cnonce="18dfcf3e4a7b809d"
But the console output of the initial request using this Ruby library shows:
note the algorithm=\"MD5\"
<- "GET /some/request HTTP/1.1\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: 10.1.0.15\r\n\r\n"
-> "HTTP/1.1 401 Unauthorized\r\n"
-> "Content-Length: 530\r\n"
-> "Server: SomeServer/5.0\r\n"
-> "Allow: GET, HEAD, POST, PUT\r\n"
-> "Date: Sun, 27 Jan 2013 00:29:23 GMT\r\n"
-> "Content-Type: text/html;charset=utf-8\r\n"
-> "Www-Authenticate: Digest realm=\"Some Realm\", nonce=\"5a8b8b46cfb84466431baf454eb9ddb9\", algorithm=\"MD5\", qop=\"auth\"\r\n"
For the script example in the original post, I would insert the following two lines:
www_auth_response = res['www-authenticate']
www_auth_response["algorithm=\"MD5\""] = "algorithm=MD5"
And Modify the third line:
auth = digest_auth.auth_header uri, www_auth_response, 'GET'
As follows:
...
res = h.request req
# res is a 401 response with a WWW-Authenticate header
www_auth_response = res['www-authenticate']
www_auth_response["algorithm=\"MD5\""] = "algorithm=MD5"
auth = digest_auth.auth_header uri, www_auth_response, 'GET'
# create a new request with the Authorization header
req = Net::HTTP::Get.new uri.request_uri
req.add_field 'Authorization', auth
...
The important thing that is going on here is that we are modifying the www-authenticate string that is coming back from your initial unauthorized 401 request (as interpreted by this ruby library). Sending the modified header string (www_auth_response) to the digest_auth.auth_header method produces no errors. At least that worked for me in my script!
I hope that helps!
Matt

Ruby send email with an attachment with /usr/sbin/sendmail

I am trying to send an email with a csv file for attachement.
I do the following but I only receive an email with a empty csv file (and not with the content of it). Can you please help me on that?
I don't want to use any extra library so please don't tell me to use pony or so ;-)
to="me#exemple.com"
subject='The subject'
from='"Name" <you#exemple.com>'
description ="Desc"
csvnamefile = "/path/to/file/filename.csv"
puts value = %x[/usr/sbin/sendmail #{to} << EOF
subject: #{subject}
from: #{from}
Content-Description: "#{csvnamefile}"
Content-Type: multipart/mixed; name="#{csvnamefile}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename="#{csvnamefile}"
Description : #{description}
EOF]
Thanks
Thanks Alex. I could make it work with your informations.
The final working result looks like this:
binary = File.read(csvnamefile)
encoded = [binary].pack("m") # base64 econding
puts value = %x[/usr/sbin/sendmail #{to} << EOF
subject: #{subject}
from: #{from}
Content-Description: "#{csvnamefile}"
Content-Type: text/csv; name="#{csvnamefile}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename="#{csvnamefile}"
#{encoded}
EOF]
/usr/sbin/sendmail doesn't know anything about attachments and treats email message body according to RFC 5322 as flat US-ASCII text. To send a file as attachment you need to format your message as MIME message according to RFC 2045. For example of such a message see Appendix A to RFC 2049.

Codeigniter not sending through JangoSMTP

I am trying to send an email using codeigniter through JangoSMTP and for whatever reason the script takes about 60-70 seconds then gives me an error on the last portion of the process, the send.
Here is CI's debug..
*220 relay.jangosmtp.net ESMTP Welcome to the JangoSMTP trackable email relay system.; Thu, 05 May 2011 19:08:15 -0000
hello: 250-relay.jangosmtp.net Hello netdesk.aiwebsystems.com [173.236.184.252], pleased to meet you.
250-ENHANCEDSTATUSCODES
250-SIZE
250-EXPN
250-ETRN
250-ATRN
250-DSN
250-CHECKPOINT
250-8BITMIME
250-AUTH CRAM-MD5 PLAIN LOGIN DIGEST-MD5
250-STARTTLS
250 HELP
from: 250 2.1.0 ... Sender ok
to: 250 2.1.5 ... Recipient ok; will forward
to: 250 2.1.5 ... Recipient ok; will forward
data: 354 Enter mail, end with "." on a line by itself
The following SMTP error was encountered:
Unable to send email using PHP SMTP. Your server might not be configured to send mail using this method.
User-Agent: CodeIgniter
Date: Thu, 5 May 2011 14:08:16 -0500
From: "Ryan Thompson"
Return-Path:
To: service#aiwebsystems.com
Subject: =?utf-8?Q?Email_Test_SMTP?=
Reply-To: "service#aiwebsystems.com"
X-Sender: service#aiwebsystems.com
X-Mailer: CodeIgniter
X-Priority: 3 (Normal)
Message-ID: <4dc2f5a0b3518#aiwebsystems.com>
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Testing the email class.*
Here is the simple code I am using to send in a sandbox controller..
function smtp(){
$this->load->library('email');
$config['protocol'] = 'smtp';
$config['smtp_host'] = 'relay.jangosmtp.net';
$config['smtp_user'] = 'MYUSERNAME';
$config['smtp_pass'] = 'MYPASS';
$this->email->initialize($config);
$this->email->from('myemail#aiwebsystems.com', 'Ryan Thompson');
$this->email->to('myemail#aiwebsystems.com');
$this->email->subject('Email Test SMTP');
$this->email->message('Testing the email class.');
$this->email->send();
echo $this->email->print_debugger();
}
I tested this exact same thing with my regular email host and creds and it popped one off perfectly.
I don't have any IP restrictions or From: restrictions at Jango. I am authing only as username/pass.
My CI Version is 2.0.0
I am at the end of my wits!! My Email.php class file is un-edited.
See here
The problem is likely due to the fact that CodeIgniter defaults to using LF as the line terminator, rather than CRLF required by RFC. Add this code:
$this->config['crlf'] = '\r\n';
$this->config['newline'] = '\r\n';

Resources