Composing mail messages to send via Net::SMTP

Discussion in 'Ruby' started by Asfand Yar Qazi, Feb 10, 2005.

  1. Hi,

    I want to make a mail message with attachments and send it. However,
    its the making attachments bit that is causing me difficulty.

    The Net::SMTP docs say I should use RubyMail or TMail to compose messages

    RubyMail looked very good. It was Ruby only, so you could include the
    code in a directory with yours, and the user would not have to install
    anything (apart from Ruby, of course.) But it doesn't support adding
    of attachments very well!

    RubyMail is available here: http://www.lickey.com/rubymail/

    However, I found a small snippet of code on the net, which I thought
    would work. It didn't (its the 'add_attachment' method in the code
    below.) Anyway, I'm stumped: why isn't this code working?

    #!/usr/bin/env ruby

    require 'rmail'

    module RMail

    class Message
    def add_file(path, content_type='application/octet-stream')
    part = RMail::Message::new
    part.header.set('Content-Type', content_type)
    part.header.set('Content-Disposition',
    'attachment',
    'filename' => File::basename(path))
    part.header.set('Content-Transfer-Encoding', 'base64')
    File::eek:pen(path) do |fh|
    part.body = fh.sysread(8192).unpack('a*').pack('m')
    end
    self.add_part(part)
    end
    end
    end

    msg = RMail::Message.new
    msg.body = "Hello"
    msg.header.subject = "A Subject"
    msg.header.from = ""
    msg.header.to = ""
    msg.add_file("jmparse.rb")
    msg.to_s


    Could someone please tell me a way of attaching a file to a mail
    message?! I need a portable solution that works possibly without
    custom C code (unlike TMail, which uses a C library.)

    Thanks
     
    Asfand Yar Qazi, Feb 10, 2005
    #1
    1. Advertisements

  2. Asfand Yar Qazi

    stevetuckner Guest

    Here is some code I stole from somewhere (I don't remember where now)
    and modified. First is an example of how to use it and then the code
    itself. I hope this is useful to you. By the way I think such
    functionality should be part of the base SMTP class in ruby itself
    (though not necessarily my implementation).

    Steve Tuckner

    require "mysmtp" # see below

    # build the message
    message = Net::SMTP::Message.new(fromName, fromEMail, subject, content)
    files.each do |file|
    message.attachBinaryFile(file)
    end

    # send the email
    message_array = message.format(to)
    Net::SMTP.start(mailServer, 25, "localhost.localdomain", mailUsername,
    mailPassword, :login) do |smtp|
    smtp.sendmail(message_array, message.fromEMail, to.map{|email,name|
    email})
    end

    mysmtp.rb -------------------------------- library addition
    --------------------------------------------------

    require "net/smtp"

    module Net
    class SMTP
    class Message
    attr_reader :fromEMail

    def initialize(fromName, fromEMail, subject="No Subject",
    content=[])
    @boundary = createBoundary()
    @fromName, @fromEMail = fromName, fromEMail
    @attachments = [] # or Array.new, attachments are
    stored in an array, each index is an hash (see attach method)
    @subject = subject
    @content = content
    @message = nil
    end

    def createBoundary()
    return "----=_RubySendMimeMailSmtp_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, "rb");
    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
    if filename =~ /\.pdf$/ then return "application/pdf" end
    # more types?!
    return "application/octet-stream"
    end

    private :contentType

    def format(toEMail, toName=nil)
    #if @message then
    # @message[2] = "To: #{toName} <#{toEMail}>\r\n"
    # return @message
    #end

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

    @message.push("Subject: #{@subject}\r\n")
    @message.push("From: #{@fromName} <#{@fromEMail}>\r\n")

    # format to string
    if toEMail.respond_to?:)map) then
    to_str = toEMail.map{|email, name| "#{name}
    <#{email}>"}.join(",\r\n\t")
    else
    if toName then
    to_str = "#{toName} <#{toEMail}>"
    else
    to_str = "<#{toEMail}>"
    end
    end
    @message.push("To: " + to_str + "\r\n")

    #message.push("Reply-To: #{@from}\r\n")
    #message.push("To: #{@to}\r\n")
    #message.push("Subject: #{@subject}\r\n")
    @message.push("MIME-Version: 1.0\r\n")
    # add multipart header if we have got attachments
    if (@attachments.length > 0)
    @message.push("Content-Type: multipart/mixed;
    boundary=\"#{@boundary}\"\r\n")
    @message.push("\r\n")
    @message.push("This is a multi-part message in MIME
    format.\r\n")
    @message.push("\r\n")
    end

    # add text part if given
    if (@content.length > 0)
    # add boundary if we are multiparted, otherwise just add
    text
    if (@attachments.length > 0)
    @message.push("--#{@boundary}\r\n")
    @message.push("Content-Type: text/plain;
    charset=\"iso-8859-1\"\r\n")
    @message.push("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
    @message.push("Content-Type: text/plain;
    charset=iso-8859-1\r\n")
    @message.push("Content-Transfer-Encoding: 8bit\r\n")
    end
    @message.push("\r\n")
    @content.each do |line|
    @message.push("#{line}\r\n")
    end
    @message.push("\r\n")
    end


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

    # closing boundary if multiparted
    @message.push("--#{@boundary}--\r\n") if
    (@attachments.length > 0)

    @message
    end # def sendMail()
    end
    end

    =begin
    # uncomment to see conversation with server (only works with 1.8.2)

    class SMTP
    private
    def getok( fmt, *args )
    str = sprintf(fmt, *args)
    puts "--> '#{str}'"
    res = critical {
    @socket.writeline str
    recv_response()
    }
    puts "<-- '#{res}'"
    return check_response(res)
    end

    def get_response( fmt, *args )
    str = sprintf(fmt, *args)
    puts "--> '#{str}'"
    @socket.writeline str
    str = recv_response()
    puts "<-- '#{str}'"
    str
    end
    end
    =end
    end
     
    stevetuckner, Feb 10, 2005
    #2
    1. Advertisements

  3. Sorry for the late reply, thanks for the code.
     
    Asfand Yar Qazi, Feb 13, 2005
    #3
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.