Email with attachments?

Discussion in 'Ruby' started by Shashank Date, Sep 2, 2003.

  1. 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 = ""
    mail.to = ""
    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
     
    Shashank Date, Sep 2, 2003
    #1
    1. Advertising

  2. Shashank Date

    Carlos Guest

    > 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, Sep 2, 2003
    #2
    1. Advertising

  3. "Carlos" <> 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
     
    Shashank Date, Sep 2, 2003
    #3
  4. Shashank Date

    Carlos Guest

    > 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 = ""
    mail.to = ""
    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, Sep 2, 2003
    #4
  5. "Carlos" <> 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
     
    Shashank Date, Sep 2, 2003
    #5
  6. Shashank Date

    Carlos Guest

    > > 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, Sep 2, 2003
    #6
  7. "Shashank Date" <> 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
     
    Shashank Date, Sep 4, 2003
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Aleksei Pashin

    Send email message with attachments

    Aleksei Pashin, Oct 12, 2004, in forum: ASP .Net
    Replies:
    0
    Views:
    460
    Aleksei Pashin
    Oct 12, 2004
  2. MattB

    email attachments

    MattB, Aug 30, 2004, in forum: ASP .Net
    Replies:
    2
    Views:
    522
    MattB
    Aug 31, 2004
  3. Replies:
    2
    Views:
    1,162
    Mike Meyer
    Jun 22, 2005
  4. codewarrior
    Replies:
    0
    Views:
    546
    codewarrior
    May 4, 2009
  5. Peter Green
    Replies:
    0
    Views:
    653
    Peter Green
    Nov 5, 2009
Loading...

Share This Page