Email with attachments?

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 = "sdate@everestkc.net"
mail.to = "shanko_date@yahoo.com"
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

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.

“Carlos” angus@quovadis.com.ar wrote in message

  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")

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

– shanko

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 = “angus@quovadis.com.ar”
mail.to = “angus@other.address”
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

“Carlos” angus@quovadis.com.ar wrote in message

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

mail = CyOSendMimeMailSmtp.new()
mail.server = “localhost” # localhost is also default
^^^^^^^^^^^^^^^^^^^^
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.

“Carlos” angus@quovadis.com.ar wrote in message
news:20030902134023.GA4771@quovadis.com.ar…

mail = CyOSendMimeMailSmtp.new()
mail.server = “localhost” # localhost is also default
^^^^^^^^^^^^^^^^^^^^
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.

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) from C:/ruby/lib/ruby/1.8/net/protocol.rb:182:in rbuf_fill’
from C:/ruby/lib/ruby/1.8/net/protocol.rb:160:in readuntil' from C:/ruby/lib/ruby/1.8/net/protocol.rb:171:in readline’
from C:/ruby/lib/ruby/1.8/net/smtp.rb:519:in recv_response' from C:/ruby/lib/ruby/1.8/net/smtp.rb:414:in initialize’
from C:/ruby/lib/ruby/1.8/net/smtp.rb:414:in critical' from C:/ruby/lib/ruby/1.8/net/smtp.rb:414:in initialize’
from C:/ruby/lib/ruby/1.8/net/smtp.rb:349:in new' from C:/ruby/lib/ruby/1.8/net/smtp.rb:349:in do_start’
from C:/ruby/lib/ruby/1.8/net/smtp.rb:330:in start' from C:/atest/tst_email.rb:101:in sendMail’
from C:/atest/tst_email.rb:171

Tool completed with exit code 1

“Shashank Date” sdate@everestkc.net wrote in message

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