Composing mail messages to send via Net::SMTP


A

Asfand Yar Qazi

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 = "(e-mail address removed)"
msg.header.to = "(e-mail address removed)"
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
 
Ad

Advertisements

S

stevetuckner

Asfand said:
Hi,

I want to make a mail message with attachments and send it. However,
its the making attachments bit that is causing me difficulty.
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
 
Ad

Advertisements

A

Asfand Yar Qazi

stevetuckner said:
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).

Sorry for the late reply, thanks for the code.
 

Top