Sunday, July 1, 2007

Automating Outlook with Ruby: Sending Email

Just as we have done with Excel and Word, we can use the win32ole library to automate various tasks in Microsoft Outlook. Let's take a look at how to create an new email message.

Of course, we'll want to require the win32ole library:


require 'win32ole'

Create an instance of the Outlook Application object:

outlook = WIN32OLE.new('Outlook.Application')

Outlook is a single-instance application, so if it is already running, calling the WIN32OLE.new method will behave the same as the WIN32OLE.connect method, returning the running instance.

To create a new item in Outlook, call the Application object's CreateItem method and pass it a numeric value that represents the type of item to create. Valid arguments include:

olMailItem = 0
olAppointmentItem = 1
olContactItem = 2
olTaskItem = 3
olJournalItem = 4
olNoteItem = 5
olPostItem = 6

So, to create a new email message (ie, MailItem), we call the CreateItem method with an argument of 0 (zero):

message = outlook.CreateItem(0)

This returns the newly-created mail item object. We'll proceed by setting values for this MailItem object's properties and calling some of its methods.

To define the subject line of the message, set its Subject value with a string:

message.Subject = 'Double-Header Today'

To define the message body, set the Body value to a string:

message.Body = 'This is the message body.'

Alternatively, for an HTML message body, set the HtmlBody value to an HTML-encoded string.

Define the recipient by setting the To property...

message.To = 'ted.williams@redsox.com'

...or call the Recipients.Add method one or more times:

message.Recipients.Add 'ted.williams@redsox.com'
message.Recipients.Add 'joe.dimaggio@yankees.com'

Got attachments to add? Call the Attachments.Add method one or more times, passing it the path and name of the attachment:

message.Attachments.Add('c:\my_folder\my_file.txt', 1)

The second argument, 1, indicates that this is an attached file, rather than a link to the original file.

You can save the unsent message in your Outbox by calling the Save method...

message.Save

This would then allow you to open and review the message before manually clicking the Send button.

Of course, you can send the message with the Send method:

message.Send

Note that when using the Send method, Outlook may display a security alert that forces the user -- after a forced pause of about five seconds -- to click 'Yes' to allow the message to be sent. I am unaware of a method for avoiding this alert dialog.

UPDATE: I have discovered, but not yet used, a free Outlook add-in from MAPILab that allows the end user to set Outlook security settings. And the related ($99) Security Manager claims to allow you to bypass the Outlook security alerts, with a single line of code. I can't recommend either solution, not having tried them, but mention them here for your information.

That about wraps our show for today. Would you like to see more about Automating Outlook with Ruby? As always, feel free to post a comment here or email me with questions, comments, or suggestions.

Thanks for stopping by!


Digg my article

27 comments:

Revence said...

I wonder how one can mine out the chosen default mail client ... I wouldn't want to start Outlook for someone who uses, say, Thunderbird.

Is that somewhere in the Registry?

Nice, nice job, here. Keep it up.

Christian said...

When executing the Send-method I get a message box with a warning that an application tries to send email automaticly and if I want to allow this.
Maybe this should also be handled?

Adam said...

Yeah I encountered the same thing. I tried to get around this using something like:

wsh = WIN32OLE.new('Wscript.shell')
wsh.AppActivate('Microsoft Office Outlook')
sleep(1)
wsh.SendKeys('{TAB}{TAB} {TAB}{TAB}{ENTER}')
sleep(7) # to wait for the timer
wsh.SendKeys('{TAB}{TAB}{ENTER}')

But there has got to be an easier way....

David Mullet said...

@revence:

See my new post here.

I hope that helps.

@christian & adam:

I should have included mention of this in my post, sorry. I don't know that it is possible to disable the security alert. I shall investigate further...

walter said...

The line above has message.etc assigning to attachment, but it's not used in the example.

attachment = message.Attachments.Add('c:\my_folder\my_file.txt', 1)

Probably you had in mind to assign the folder to attachment then add it to .Add method.

any other reason to use it here?

cheers
walter

David Mullet said...

@walter:

Actually, there was no reason for me to assign to a variable. The line should have read:

message.Attachments.Add('c:\my_folder\my_file.txt', 1)

I have corrected this in the post. Thanks for calling that to my attention!

David

Anonymous said...

Hi.

Thanks for the article. Do you know if this script will work with Outlook Express(?)

-Katie

David Mullet said...

@Katie-

Unfortunately, Outlook Express does not expose its objects for COM/OLE automation, so this script could not be adapted for Outlook Express.

David

rethas said...

Thank you for this utility. saved my day. Just to build on this further, is there a way to run multiple instances of the same ruby script. I would like to use this for some testing purposes where I am sending say 10 mails every second.

any thoughts?

Anonymous said...

For Katie:

if your default email program is Outlook Express, you can get around the lack of COM support by calling a mailto: tag somewhere. Thus running Outlook Express.

As I recall, in python you can use the webbrowser module to call a mailto tag, then form the tag with all your data with string concatenation.

Not sure how to do the same in Ruby
however this approach should work for your case.

cheers
walter

Anonymous said...

For Katie:

Here's a followup example python script. Please anyone post back with a Ruby equivalent, I'd appreciate it ;)

#script uses mailto tag to make
#email for Outlook Express users
#returns in body are replaced with
#linefeeds to remove OE error

import webbrowser

username = raw_input("Type the user's username: ")
email = raw_input("Type the user's email address: ")
subject = "Payment is Due"


body = r"""Dear Valued Customer,

We have noticed that you owe us money, please pay us now $"%s".

Regards,

Customer Service Department
""" %(amount_owed)

# replace return with line feed
body = body.replace("\n","%0A")

#make email
uri = r"mailto:%(email)s?subject=%(subject)s&body=%(body)s" %{'email' : email, 'subject':subject, 'body':body}

#send email to outlook express
webbrowser.open(uri)

j.erik said...

Hi,

really really usefull article for me. Do you know perhaps how i can access the outlook addressbook?

cheers -- jerik

David Mullet said...

@j.erik:

If you mean Outlook Contacts, see my new post about it here.

David

Anonymous said...

Hi,
thank for your useful outlook blogs.
I'm looking to find the hook for monitoring and filtering outlook mails. Basically, i'd like to watch and filter incoming emails (realtime) so i can apply rules on it (think procmail in *nix).

thank you and kind regards - botp

j.erik said...

@david:

No, not the contacts, I meand the address book. (Shortcut on Outlook is : STRG + SHIFT + b)

I want to make an easier and better search. The one in outlook, takes me to long to query for persons...

cheers -- jerik

David Mullet said...

@jerik:

I have just posted a new article here, discussing the Outlook Address Books.

David

cmcevoy said...

I have previously used a library called Redemption for bypassing the oh-so-irritating 'click confirm to send' dialog. I don't know how it would interface with Ruby, but I will have a go to see if it works.
http://www.dimastr.com/redemption/

Jim said...

David, do you happen to have any links to documentation on scripting Outlook with ruby? Google is turning up nothing (but this post here).

David Mullet said...

Jim-

Well, I have several articles here tagged "outlook".

Let me know if there's something specific you need that's not covered here, and I'll do what I can to help.

David

Jim said...

Beautiful. This is almost exactly what I was looking for.

Saumya said...

Hey David,
I am a long time fan of this blog, and an amateur in Ruby :). After a long time I finally worked out how to copy an excel table/selected range to an outlook mail, along with the formatting of the table...Hope this code adds some value to others ;)... cheers!

...
$ws.Range("A1:C5").copy
outlook = WIN32OLE.new('Outlook.Application')
message = outlook.CreateItem(0)

message.To = abc@xyz.com
message.Subject = "Copy excel table test"
message.Display
word = WIN32OLE.connect('Word.Application')
message.HTMLBody =
''
word.Selection.TypeText "Hi All,"
word.Selection.TypeParagraph
word.Selection.TypeParagraph
word.Selection.TypeText "This test will copy the excel table here:"
word.Selection.TypeParagraph
word.Selection.TypeParagraph
word.Selection.PasteExcelTable false, false, false

word.Selection.WholeStory
word.Selection.Font.Name = 'Calibri' #whichever font you want
word.Selection.Font.Size = 11
''
sleep 2
message.save

PS: Any reviews or improvements are welcome, it'll add to my learning.

Saumya said...

Just to add:
If you are using Outlook 2007, the above code is not able to insert text/table in the email directly, since 2007 doesn't create an instance of Word application (which 2003 does, it can be seen under task manager - processes). So you need to tweak it a li'l bit using outlook 2007's 'Inspector'and 'WordEditor', as follows:

...
message.Display
selec = outlook.ActiveInspector.WordEditor.Windows(1).Selection

message.HTMLBody =
''
selec.TypeText "Hi All,"

...

Note: I also observed that on using message.Send method in outlook 2007, the security pop-up "A program is trying to send email on your behalf" is not displayed, and the email is sent successfully. I didn't use any third party tool or dlls like 'ClickYes' or Redemption.
Any ideas?

agathe said...

Hi everybody,

I'm trying to modify the mail of the sender and of the reply. I just triend that :

outlook = WIN32OLE.new('Outlook.Application')
message = outlook.CreateItem(olMailItem)
message.Subject = subject
message.Body = body
message.To = user_to.mail
message.SenderEmailAddress = user_from.mail
message.Save
message.Send

because i don't see an other way to do it.

The problem is it gives me this error message :

OLE error code:4096 in Microsoft Office Outlook
Property is read-only.
HRESULT error code:0x80020009
Exception occurred.

I researched in the Outlook VB window and it's really read-only.
Does anybody know how to modify it anyway ? or an idea ?

Or if it's not possible, how to use the outlook of the user computer to have his mail?

Thank you in advance :)

Anonymous said...

Hi!

I found this article very helpful but ran into my own problem. I'm trying to move the draft copy created when I 'mail.Save' to a folder outside of Outlook. I can do this manually but haven't worked out a way to have it done in the block.

Thanks,
HP

David Mullet said...

@Anonymous:

Try calling the SaveAs() method on the MailItem object. This method takes two arguments: the path and filename, and an integer specifying the filetype.

olRTF = 1
olMSG = 3
olHTML = 5

msg.SaveAs('c:\temp\Message.htm', olHTML)

-David

Anonymous said...

Excellent blog - thanks.
Q: any idea if it's possible to do the same without having outlook running? Cheers

Kshitij Mehta said...

Great Article,
FYI if you want to disable the warnings, go to (options/truscenter/trustcenter settings/programmatic access) and check if there is a "Anti-Virus Status is VAlid" if not you should install an antivirus EG:Sophos. Once you do that and restart your Outlook,the Warnings will Go Away !!!