Friday, December 7, 2007

How to Format Ruby Code in a Blog Post

My previous post was the first time I had ever presented ruby code in a post. This article saved me the hassle of figuring it out how to make it look pretty (I stuck the css directly into my blogger template). Thanks Wolfmann.

Sending GMail from a Standalone Ruby Script

As part of my rails application, I need to send emails from a stand alone ruby script. For example, the back end of my rails application is a (currently) small postgres database. The easiest way to back it up is to send a database dump to myself via gmail.

There are several tutorials on how to send gmail directly from a rails app. Presented here is a method for doing it from a standalone script. This method assumes rails is installed, but the script itself runs outside of any rails application. In my case in the wee hours of the morning as a cron job.

Usage of this script would be something like:
require 'send_gmail'
hsh={:to=>'name@domain.com', :subject=>'subject', :body=>'body'}
SendGMail.send_gmail(hsh)

The script can also handle attachments, albeit in an unusual way. Instead of giving it a file, you give it the data directly, along with a filename, mime_type, and transfer encoding:
raw_attachment={:mime_type=>'application/x-gzip', :body=>make_body, :transfer_encoding=>'base64', :filename=>'backup.tar.gz' }
hsh[:raw_attachment]=raw_attachment

It should be easy enough to modify the script to handle files as attachments directly, but I didn't have such a need.

Here is the complete listing. My thanks to Stephun Chu, as most of this is cribbed from his aforementioned post.

Edit: Fixed syntax error - Thanks Nolan!  

send_gmail.rb


#!/usr/bin/env ruby
require 'rubygems'
gem 'actionmailer'
require 'action_mailer'
require 'openssl'
require 'net/smtp'

module SendGMail

@user_name='username@domain.com'
@domain='domain.com'
@password='password'


def SendGMail.send_gmail(hsh)

raw_attachments=hsh.fetch(:raw_attachements, [])
if hsh.has_key?(:raw_attachment)
raw_attachments.push(hsh[:raw_attachment])
end

mail=TMail::Mail.new
mail.to=hsh[:to]
mail.date=Time.now
mail.from=@user_name
mail.subject=hsh[:subject]

main=mail
main=TMail::Mail.new
main.body = hsh[:body]
puts main.body
main.set_content_type('text/plain', nil, 'charset'=>'utf-8')
mail.parts.push(main)

for raw_attachment in raw_attachments
part = TMail::Mail.new
transfer_encoding=raw_attachment[:transfer_encoding]
body=raw_attachment[:body]
case (transfer_encoding || "").downcase
when "base64" then
part.body = TMail::Base64.folding_encode(body)
when "quoted-printable"
part.body = [body].pack("M*")
else
part.body = body
end

part.transfer_encoding = transfer_encoding
part.set_content_type(raw_attachment[:mime_type], nil, 'name' => raw_attachment[:filename])
part.set_content_disposition("attachment", "filename"=>raw_attachment[:filename])
mail.parts.push(part)
end

mail.set_content_type('multipart', 'mixed')
ActionMailer::Base.deliver(mail)

end

ActionMailer::Base.smtp_settings = {
:address => 'smtp.gmail.com',
:domain => @domain,
:authentication => :plain,
:port => 587,
:user_name => @user_name,
:password => @password
}

Net::SMTP.class_eval do
private
def do_start(helodomain, user, secret, authtype)
raise IOError, 'SMTP session already started' if @started
check_auth_args user, secret, authtype if user or secret

sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
@socket = Net::InternetMessageIO.new(sock)
@socket.read_timeout = 60 #@read_timeout
@socket.debug_output = STDERR #@debug_output

check_response(critical { recv_response() })
do_helo(helodomain)

raise 'openssl library not installed' unless defined?(OpenSSL)
starttls
ssl = OpenSSL::SSL::SSLSocket.new(sock)
ssl.sync_close = true
ssl.connect
@socket = Net::InternetMessageIO.new(ssl)
@socket.read_timeout = 60 #@read_timeout
@socket.debug_output = STDERR #@debug_output
do_helo(helodomain)

authenticate user, secret, authtype if user
@started = true
ensure
unless @started
# authentication failed, cancel connection.
@socket.close if not @started and @socket and not @socket.closed?
@socket = nil
end
end

def do_helo(helodomain)
begin
if @esmtp
ehlo helodomain
else
helo helodomain
end
rescue Net::ProtocolError
if @esmtp
@esmtp = false
@error_occured = false
retry
end
raise
end
end

def starttls
getok('STARTTLS')
end

def quit
begin
getok('QUIT')
rescue EOFError, OpenSSL::SSL::SSLError
end
end
end
end