Email with attachments?

S

Shashank Date

I have searched RAA and ruby-talk (esp. [40428]) and I have
still not managed to find a simple solution to "sending email with
binary attachments" on Windows. The code given in ruby-talk
[40429] is badly mangled and I tried resurrecting it , but am not
sure about lines 37 thru 57 (if they do not get further mangled).

I get the following error:

error on sending: The remote adapter is not compatible - connect(2)

Also, does the ability to "create MIME e-mail" automatically
imply attachment functionality ? If yes, then I guess I need to
figure it out with Tmail and RubyMail.

Any suggestions?
TIA,
-- shanko


# ---------------------- line 0 --------------------------------
# File: CyOSendMimeMailSmtp.rb
# Author: Oliver Mensinger
# Version: 2002-01-22
# Status: Usable and stable
# (for simple mails incl. binary attachm.)
#
# Description:
# Create Mime mail with text and attachments and
# send it via SMTP
#
# Changes:
# 2002-01-22 After Ian reported a problem, the mail
# server address is now given to Net::SMTP::new
# instead to Net:SMTP::start
#
# Supported functions: text part, binary attachments
# read from file
# To do: html part, binary attachment given to method
# as parameter, several recipients(cc, bcc)

require "net/smtp"
require "getoptlong"

class CyOSendMimeMailSmtp
attr_accessor :text, :server,:from,:to,:subject

def initialize()
@boundary = createBoundary()
# attachments are stored in an array,
# each index is an hash see attach method
@attachments = []
@serverlocalhost = ""
@subject = "no subject"
@from = @to = @text = ""
end

# ---------------- line 37 -------------

def createBoundary()
return ["----=_CyOSendMimeMailSmtp_Part_"] +
uniqueNumber()
end

private :createBoundary

# create an unique number, length variables
def uniqueNumber()
return [
sprintf("%02X", rand(999999990000000)), # random part
sprintf("%02X", Time.new.to_i), # machine time
sprintf("%02X", $_), # process
number
sprintf("%02X", Time.new.usec()) # micro seconds of machine
]
end

private :uniqueNumber

# ---------------- line 57 -------------

# attachBinaryFile(phy_filename, real_filename)
# adds a file and converts it to base64
# phy_filename is the physical filenameincl. path) of a file that must
exist
# real_filename will be the name of the file in the mail message
# if real_filename _is not given_, it will be the physical filename
# returns true if file exists and was attached to mail, otherwise false

def attachBinaryFile(phy_filename, real_filename)
# read file into string and convert it to base64
begin
f = File.new(phy_filename);
data = f.read()
f.close()
rescue
return false
end

data = [data].pack("m*");

real_filename = phy_filename if (real_filename=="")

# the very special problem of phy_filename and real_filename:
# the physical filename could be something like
# tmp/12367672647342342,
# as an external binary file stored outside of a database, where the
# real filename is the original filename which is stored in the database
# so we take the real_filename for determining the files type

attachment = {"type" => contentType(real_filename),
"name" => File.basename(real_filename),
"data" => data }

@attachments.push(attachment)
end


def contentType(filename)
filename = File.basename(filename).downcase
return "image/jpg" if (filename =~ /\.jp(e?)g$/)
return "image/gif" if (filename =~ /\.gif$/)
return "text/html" if (filename =~ /\.htm(l?)$/)
return "text/plain" if (filename =~ /\.txt$/ )
return "application/zip" if (filename =~ /\.zip$/)
# more types?!
return "application/octet-stream"
end

private :contentType

def sendMail()

raise "mail server not specified" if(@server.length == 0)
raise "sender address not specified" if(@from.length == 0)
raise "receiver address not specified" if(@to.length == 0)

#raise "nothing to send" if((@text.length==0)&(@attachments.length==0))

smtp = Net::SMTP.new(@server)
smtp.start()
smtp.ready(@from, @to) do |wa|
wa.write("Reply-To:{@from}\r\n")
wa.write("To:{@to}\r\n")
wa.write("Subject:{@subject}\r\n")
wa.write("MIME-Version:.0\r\n")
# add multipart header if we have got attachments
if(@attachments.length)
wa.write("Content-Type: multipart/mixed;
boundary=\"#{@boundary}\"\r\n")
wa.write("\r\n")
wa.write("This is a multi-part message in MIME format.\r\n")
wa.write("\r\n")
end

# add text part if given
if(@text.length)
# add boundary if we are multiparted, otherwise just add text
if(@attachments.length)
wa.write("--#{@boundary}\r\n")
wa.write("Content-Type: text/plain; charset=\"iso-8859-1\"\r\n")
wa.write("Content-Transfer-Encoding:bit\r\n")
# we don't take care of very old mail servers with bit only
else
# if only text and no attachm. we give the encoding
wa.write("Content-Type: text/plain; charset=iso-8859-1\r\n")
wa.write("Content-Transfer-Encoding:bit\r\n")
end
wa.write("\r\n")
wa.write("#{@text}\r\n")
wa.write("\r\n")
end


# add attachments if given
if(@attachments.length)
@attachments.each do |part|
wa.write("--#{@boundary}\r\n")
wa.write("Content-Type:{part['type']};
name=\"#{part['name']}\"\r\n")
wa.write("Content-Transfer-Encoding: base64\r\n")
wa.write("Content-Disposition: attachment;
filename=\"#{part['name']}\"\r\n")
wa.write("\r\n")
wa.write("#{part['data']}") # no more need for \r\n here!
wa.write("\r\n")
end
end

# closing boundary if multiparted
wa.write("--#{@boundary}--\r\n") if(@attachments.length)
end # smtp.ready(...)
end # def sendMail()
end # class CyOSendMimeMailSmtp


if $0 == __FILE__
# example for using CyOSendMimeMailSmtp class
# require "CyOSendMimeMailSmtp.rb"

mail = CyOSendMimeMailSmtp.new()
mail.server = "pop3.everestkc.net"
mail.from = "(e-mail address removed)"
mail.to = "(e-mail address removed)"
mail.subject = "1st test using class CyOSendMimeMailSmtp"
mail.text = "I love Ruby!\n2nd line."

#if(!mail.attachBinaryFile("/home/cyo/ruby.jpg"))
# puts "could not attach file"
#end

begin
mail.sendMail()
rescue
puts "error on sending: #{$!}"
end

end
 
C

Carlos

I have searched RAA and ruby-talk (esp. [40428]) and I have
still not managed to find a simple solution to "sending email with
binary attachments" on Windows. The code given in ruby-talk
[40429] is badly mangled and I tried resurrecting it , but am not
sure about lines 37 thru 57 (if they do not get further mangled).

I get the following error:

error on sending: The remote adapter is not compatible - connect(2)

Also, does the ability to "create MIME e-mail" automatically
imply attachment functionality ? If yes, then I guess I need to
figure it out with Tmail and RubyMail.


I don't know what generates the error you get, but you have some bugs here:

def sendMail()

raise "mail server not specified" if(@server.length == 0)
raise "sender address not specified" if(@from.length == 0)
raise "receiver address not specified" if(@to.length == 0)

#raise "nothing to send"
if((@text.length==0)&(@attachments.length==0))

smtp = Net::SMTP.new(@server)
smtp.start()
smtp.ready(@from, @to) do |wa|
wa.write("Reply-To:{@from}\r\n")

^^^^^^--- should be #{@from}
wa.write("To:{@to}\r\n")

^^^^^--- should be #{@to}
wa.write("Subject:{@subject}\r\n")

^^^^^^^^^^-- #{@subject}
wa.write("MIME-Version:.0\r\n")


Add the '#' and maybe will work.
 
S

Shashank Date

Carlos said:
^^^^^^--- should be #{@from}


^^^^^--- should be #{@to}


^^^^^^^^^^-- #{@subject}

Thanks for pointing that out ... I corrected them and some more
minor issues and still not able to make it work. :-((

-- shanko
 
C

Carlos

Thanks for pointing that out ... I corrected them and some more
minor issues and still not able to make it work. :-((

Here is the original script, with superfluous linefeeds removed (and
different sender, etc., for testing purposes). It works for me...


require "net/smtp"
require "getoptlong"

# File: CyOSendMimeMailSmtp.rb
# Author: Oliver Mensinger
# Version: 2002-01-22
# Status: Usable and stable for simple mails (incl. binary attachm.)
#
# Description:
# Create Mime mail with text and attachments and send it via SMTP
#
# Changes:
# 2002-01-22 After Ian reported a problem, the mail server address is now given
# to Net::SMTP::new instead to Net:SMTP::start
#
# Supported functions: text part, binary attachments read from file
# To do: html part, binary attachment given to method as parameter, several recipients (cc, bcc)

class CyOSendMimeMailSmtp
attr_accessor :text
attr_accessor :server, :from, :to, :subject

def initialize()
@boundary = createBoundary()
@attachments = [] # or Array.new, attachments are stored in an array, each index is an hash (see attach method)
@server = "localhost"
@subject = "no subject"
@from = @to = @text = ""
end


def createBoundary()
return "----=_CyOSendMimeMailSmtp_Part_" + uniqueNumber()
end

private :createBoundary

# create an unique number, length variables

def uniqueNumber()
return sprintf("%02X", rand(99999999 - 10000000) + 10000000) + # random part
sprintf("%02X", Time.new.to_i) + # machine time
sprintf("%02X", $$) + # process number
sprintf("%02X", Time.new.usec()) # micro seconds of machine
end

private :uniqueNumber

# attachBinaryFile(phy_filename, real_filename = "")
# adds a file and converts it to base64
# phy_filename is the physical filename (incl. path) of a file that must exist
# real_filename will be the name of the file in the mail message
# if real_filename _is not given_, it will be the physical filename
# returns true if file exists and was attached to mail, otherwise false

def attachBinaryFile(phy_filename, real_filename = "")
# read file into string and convert it to base64
begin
f = File.new(phy_filename);
data = f.read()
f.close()
rescue
return false
end

data = [data].pack("m*");

real_filename = phy_filename if real_filename == ""

# the very special problem of phy_filename and real_filename:
# the physical filename could by something like /tmp/12367672647342342,
# as an external binary file stored outside of a database, where the
# real filename is the original filename which is stored in the database.
# so we take the real_filename for determining the files type
attachment = { "type" => contentType(real_filename), "name" => File.basename(real_filename), "data" => data }
@attachments.push(attachment)
end


def contentType(filename)
filename = File.basename(filename).downcase
if filename =~ /\.jp(e?)g$/ then return "image/jpg" end
if filename =~ /\.gif$/ then return "image/gif" end
if filename =~ /\.htm(l?)$/ then return "text/html" end
if filename =~ /\.txt$/ then return "text/plain" end
if filename =~ /\.zip$/ then return "application/zip" end
# more types?!
return "application/octet-stream"
end

private :contentType

def sendMail()
raise "mail server not specified" if @server.length == 0
raise "sender address not specified" if @from.length == 0
raise "receiver address not specified" if @to.length == 0
#raise "nothing to send" if (@text.length == 0) && (@attachments.length == 0)

smtp = Net::SMTP.new(@server)
smtp.start()
smtp.ready(@from, @to) do |wa|
wa.write("Reply-To: #{@from}\r\n")
wa.write("To: #{@to}\r\n")
wa.write("Subject: #{@subject}\r\n")
wa.write("MIME-Version: 1.0\r\n")
# add multipart header if we have got attachments
if (@attachments.length > 0)
wa.write("Content-Type: multipart/mixed; boundary=\"#{@boundary}\"\r\n")
wa.write("\r\n")
wa.write("This is a multi-part message in MIME format.\r\n")
wa.write("\r\n")
end

# add text part if given
if (@text.length > 0)
# add boundary if we are multiparted, otherwise just add text
if (@attachments.length > 0)
wa.write("--#{@boundary}\r\n")
wa.write("Content-Type: text/plain; charset=\"iso-8859-1\"\r\n")
wa.write("Content-Transfer-Encoding: 8bit\r\n") # we don't take care of very old mail servers with 7 bit only
else
# if only text and no attachm. we give the encoding
wa.write("Content-Type: text/plain; charset=iso-8859-1\r\n")
wa.write("Content-Transfer-Encoding: 8bit\r\n")
end
wa.write("\r\n")
wa.write("#{@text}\r\n")
wa.write("\r\n")
end


# add attachments if given
if (@attachments.length > 0)
@attachments.each do |part|
wa.write("--#{@boundary}\r\n")
wa.write("Content-Type: #{part['type']}; name=\"#{part['name']}\"\r\n")
wa.write("Content-Transfer-Encoding: base64\r\n")
wa.write("Content-Disposition: attachment; filename=\"#{part['name']}\"\r\n")
wa.write("\r\n")
wa.write("#{part['data']}") # no more need for \r\n here!
wa.write("\r\n")
end
end

# closing boundary if multiparted
wa.write("--#{@boundary}--\r\n") if (@attachments.length > 0)
end # smtp.ready(...)
end # def sendMail()
end # class CyOSendMimeMailSmtp


#=begin
# example for using CyOSendMimeMailSmtp class
#require "CyOSendMimeMailSmtp.rb"

mail = CyOSendMimeMailSmtp.new()
mail.server = "localhost" # localhost is also default
mail.from = "(e-mail address removed)"
mail.to = "(e-mail address removed)"
mail.subject = "1st test using class CyOSendMimeMailSmtp" # default is "no subject"
mail.text = "I love Ruby!\n2nd line."


if (!mail.attachBinaryFile("/home/angus/desktop0901.png"))
puts "could not attach file"
end


begin
mail.sendMail()
rescue
puts "error on sending: #{$!}"
end
#=end
 
S

Shashank Date

Carlos said:
Here is the original script, with superfluous linefeeds removed (and
different sender, etc., for testing purposes). It works for me...

Great ! I will take your word for it ... until I figure out how to
start a SMTP server on my machine (Windows XP Pro).

[snip]
def uniqueNumber()
return sprintf("%02X", rand(99999999 - 10000000) + 10000000) + # random part
sprintf("%02X", Time.new.to_i) + # machine time
sprintf("%02X", $$) + # process number
sprintf("%02X", Time.new.usec()) # micro seconds of machine
end

This was the part which I had got all messed up. Everything else seemed
to be OK (after you corrected me the first time).

[snip]
mail = CyOSendMimeMailSmtp.new()
mail.server = "localhost" # localhost is also default
^^^^^^^^^^^^^^^^^^^^
And this is what I am still struggling with. Any ideas are highly welcome.
Again thank you very much, Carlos !

-- shanko
 
C

Carlos

mail = CyOSendMimeMailSmtp.new()
^^^^^^^^^^^^^^^^^^^^
And this is what I am still struggling with. Any ideas are highly welcome.

Just put the name of the smtp server you normally use to send mail. I guess
it should be 'mail.everestkc.net', or something like that.
 
S

Shashank Date

Shashank Date said:
That was the first thing I tried ... used "smtp.everestkc.net" but it gave
me the following error:
so I thought it must be something else. What do you say ?

C:/ruby/lib/ruby/1.8/net/protocol.rb:188:in `on_read_timeout': socket read
timeout (60 sec) (TimeoutError)

[snip]

I tried changing the read timeout variable @read_timeout (line 270 of
C:/ruby/lib/ruby/1.8/net/smtp.rb) to 120 (from 60) but got the same error.

Any ideas, gurus ?

TIA,
-- shanko
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top