Tuesday, October 23, 2007

Send Outlook Meeting Requests with System.Net.Mail

Here's a class I developed to send Outlook Meeting Requests via SMTP Email using the System.Net.Mail library. Just instantiate the AppointmentBLL object, passing in the desired parameters for the meeting, then call EmailAppointment to actually send it.

This ended up being more difficult than I anticipated. You have to create a "multipart/alternative" message, and the calendar item is in VCALENDAR format. After some fiddling with the header parameters and VCALENDAR file, I finally got it to work, and that was after wasting a bunch of time with CDOSYS and MAPI. Enjoy...



Imports Microsoft.VisualBasic
Imports System.Net.Mail

Public Class AppointmentBLL
' This class formats and sends a meeting request via SMTP email

Public StartDate As DateTime
Public EndDate As DateTime
Public Subject As String
Public Summary As String
Public Location As String
Public AttendeeName As String
Public AttendeeEmail As String
Public OrganizerName As String
Public OrganizerEmail As String

Public Sub New(ByVal pdtStartDate As DateTime, _
ByVal pdtEndDate As DateTime, _
ByVal psSubject As String, _
ByVal psSummary As String, _
ByVal psLocation As String, _
ByVal psAttendeeName As String, _
ByVal psAttendeeEmail As String, _
ByVal psOrganizerName As String, _
ByVal psOrganizerEmail As String)

' Copy constructor parameters to public propeties

StartDate = pdtStartDate
EndDate = pdtEndDate
Subject = psSubject
Summary = psSummary
Location = psLocation
AttendeeName = psAttendeeName
AttendeeEmail = psAttendeeEmail
OrganizerName = psOrganizerName
OrganizerEmail = psOrganizerEmail
End Sub


Public Sub EmailAppointment()

' Send the calendar message to the attendee

Dim loMsg As New MailMessage
Dim loTextView As AlternateView = Nothing
Dim loHTMLView As AlternateView = Nothing
Dim loCalendarView As AlternateView = Nothing
Dim loSMTPServer As New SmtpClient

' SMTP settings set up in web.config such as:
' <system.net>
' <mailSettings>
' <smtp>
' <network
' host = "exchange.mycompany.com"
' port = "25"
' userName = "username"
' password="password" />
' </smtp>
' </mailSettings>
' </system.net>

' Set up the different mime types contained in the message
Dim loTextType As System.Net.Mime.ContentType = New System.Net.Mime.ContentType("text/plain")
Dim loHTMLType As System.Net.Mime.ContentType = New System.Net.Mime.ContentType("text/html")
Dim loCalendarType As System.Net.Mime.ContentType = New System.Net.Mime.ContentType("text/calendar")

' Add parameters to the calendar header
loCalendarType.Parameters.Add("method", "REQUEST")
loCalendarType.Parameters.Add("name", "meeting.ics")

' Create message body parts
loTextView = AlternateView.CreateAlternateViewFromString(BodyText(), loTextType)
loMsg.AlternateViews.Add(loTextView)

loHTMLView = AlternateView.CreateAlternateViewFromString(BodyHTML(), loHTMLType)
loMsg.AlternateViews.Add(loHTMLView)

loCalendarView = AlternateView.CreateAlternateViewFromString(VCalendar(), loCalendarType)
loCalendarView.TransferEncoding = Net.Mime.TransferEncoding.SevenBit
loMsg.AlternateViews.Add(loCalendarView)

' Adress the message

loMsg.From = New MailAddress(OrganizerEmail)
loMsg.To.Add(New MailAddress(AttendeeEmail))
loMsg.Subject = Subject

' Send the message
loSMTPServer.DeliveryMethod = SmtpDeliveryMethod.Network
loSMTPServer.Send(loMsg)
End Sub

Public Function BodyText() As String

' Return the Body in text format

Const BODY_TEXT = _
"Type:Single Meeting" & vbCrLf & _
"Organizer: {0}" & vbCrLf & _
"Start Time:{1}" & vbCrLf & _
"End Time:{2}" & vbCrLf & _
"Time Zone:{3}" & vbCrLf & _
"Location: {4}" & vbCrLf & _
vbCrLf & _
"*~*~*~*~*~*~*~*~*~*" & vbCrLf & _
vbCrLf & _
"{5}"

Return String.Format(BODY_TEXT, _
OrganizerName, _
StartDate.ToLongDateString & " " & StartDate.ToLongTimeString, _
EndDate.ToLongDateString & " " & EndDate.ToLongTimeString, _
System.TimeZone.CurrentTimeZone.StandardName, _
Location, _
Summary)

End Function

Public Function BodyHTML() As String

' Return the Body in HTML format

Const BODY_HTML = _
"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN"">" & vbCrLf & _
"<HTML>" & vbCrLf & _
"<HEAD>" & vbCrLf & _
"<META HTTP-EQUIV=""Content-Type"" CONTENT=""text/html; charset=utf-8"">" & vbCrLf & _
"<META NAME=""Generator"" CONTENT=""MS Exchange Server version 6.5.7652.24"">" & vbCrLf & _
"<TITLE>{0}</TITLE>" & vbCrLf & _
"</HEAD>" & vbCrLf & _
"<BODY>" & vbCrLf & _
"<!-- Converted from text/plain format -->" & vbCrLf & _
"<P><FONT SIZE=2>Type:Single Meeting<BR>" & vbCrLf & _
"Organizer:{1}<BR>" & vbCrLf & _
"Start Time:{2}<BR>" & vbCrLf & _
"End Time:{3}<BR>" & vbCrLf & _
"Time Zone:{4}<BR>" & vbCrLf & _
"Location:{5}<BR>" & vbCrLf & _
"<BR>" & vbCrLf & _
"*~*~*~*~*~*~*~*~*~*<BR>" & vbCrLf & _
"<BR>" & vbCrLf & _
"{6}<BR>" & vbCrLf & _
"</FONT>" & vbCrLf & _
"</P>" & vbCrLf & _
vbCrLf & _
"</BODY>" & vbCrLf & _
"</HTML>"

Return String.Format(BODY_HTML, _
Summary, _
OrganizerName, _
StartDate.ToLongDateString & " " & StartDate.ToLongTimeString, _
EndDate.ToLongDateString & " " & EndDate.ToLongTimeString, _
System.TimeZone.CurrentTimeZone.StandardName, _
Location, _
Summary)

End Function

Public Function VCalendar() As String

' Return the Calendar text in vCalendar format, compatible with most calendar programs

Const lcDateFormat = "yyyyMMddTHHmmssZ"
Dim loGUID As Guid = Guid.NewGuid ' Or use the guid of an exiting meeting?

Const VCAL_FILE = "BEGIN:VCALENDAR" & vbCrLf & _
"METHOD:REQUEST" & vbCrLf & _
"PRODID:Microsoft CDO for Microsoft Exchange" & vbCrLf & _
"VERSION:2.0" & vbCrLf & _
"BEGIN:VTIMEZONE" & vbCrLf & _
"TZID:(GMT-06.00) Central Time (US & Canada)" & vbCrLf & _
"X-MICROSOFT-CDO-TZID:11" & vbCrLf & _
"BEGIN:STANDARD" & vbCrLf & _
"DTSTART:16010101T020000" & vbCrLf & _
"TZOFFSETFROM:-0500" & vbCrLf & _
"TZOFFSETTO:-0600" & vbCrLf & _
"RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU" & vbCrLf & _
"END:STANDARD" & vbCrLf & _
"BEGIN:DAYLIGHT" & vbCrLf & _
"DTSTART:16010101T020000" & vbCrLf & _
"TZOFFSETFROM:-0600" & vbCrLf & _
"TZOFFSETTO:-0500" & vbCrLf & _
"RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU" & vbCrLf & _
"END:DAYLIGHT" & vbCrLf & _
"END:VTIMEZONE" & vbCrLf & _
"BEGIN:VEVENT" & vbCrLf & _
"DTSTAMP:{8}" & vbCrLf & _
"DTSTART:{0}" & vbCrLf & _
"SUMMARY:{7}" & vbCrLf & _
"UID:{5}" & vbCrLf & _
"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=""{9}"":MAILTO:{9}" & vbCrLf & _
"ACTION;RSVP=TRUE;CN=""{4}"":MAILTO:{4}" & vbCrLf & _
"ORGANIZER;CN=""{3}"":mailto:{4}" & vbCrLf & _
"LOCATION:{2}" & vbCrLf & _
"DTEND:{1}" & vbCrLf & _
"DESCRIPTION:{7}\N" & vbCrLf & _
"SEQUENCE:1" & vbCrLf & _
"PRIORITY:5" & vbCrLf & _
"CLASS:" & vbCrLf & _
"CREATED:{8}" & vbCrLf & _
"LAST-MODIFIED:{8}" & vbCrLf & _
"STATUS:CONFIRMED" & vbCrLf & _
"TRANSP:OPAQUE" & vbCrLf & _
"X-MICROSOFT-CDO-BUSYSTATUS:BUSY" & vbCrLf & _
"X-MICROSOFT-CDO-INSTTYPE:0" & vbCrLf & _
"X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY" & vbCrLf & _
"X-MICROSOFT-CDO-ALLDAYEVENT:FALSE" & vbCrLf & _
"X-MICROSOFT-CDO-IMPORTANCE:1" & vbCrLf & _
"X-MICROSOFT-CDO-OWNERAPPTID:-1" & vbCrLf & _
"X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:{8}" & vbCrLf & _
"X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:{8}" & vbCrLf & _
"BEGIN:VALARM" & vbCrLf & _
"ACTION:DISPLAY" & vbCrLf & _
"DESCRIPTION:REMINDER" & vbCrLf & _
"TRIGGER;RELATED=START:-PT00H15M00S" & vbCrLf & _
"END:VALARM" & vbCrLf & _
"END:VEVENT" & vbCrLf & _
"END:VCALENDAR" & vbCrLf

Return String.Format(VCAL_FILE, _
StartDate.ToUniversalTime().ToString(lcDateFormat), _
EndDate.ToUniversalTime().ToString(lcDateFormat), _
Location, _
OrganizerName, _
OrganizerEmail, _
"{" & loGUID.ToString() & "}", _
Summary, _
Subject, _
Now.ToUniversalTime().ToString(lcDateFormat), _
AttendeeEmail)
End Function

End Class

31 comments:

Unknown said...

Chuck,

Your VB program using the VCalendar looks outstanding.
Do you know if there is a C# version available, otherwise I will need to convert it, but at least the details are there, which is 80% of the work.
Thanks a lot anyway, but a C# version would be 100%

regards,

Jim

Chris Felstead said...

Chuck,

This post it just brilliant. I've been fighting with this for almost a week (trying the reverse engineering route).

Thank you for sharing your work with the world.

Chris.

Chuck Spohr said...

I don't have a C# version, but some of the code was inspired by C# code I picked up the MSDN forums:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2323533&SiteID=1

Good Luck!

Chuck

CHIRAG PATEL said...

Van anybody tells me the code for delete appointment from local outllok calender

Abhinav Moudgil said...

hi great post dear ...
I want a small favour from you .. I also own a blog but I am unable to add coding in that as it gives an error that HTML coding is not allowed .
So kindly let me know how can I add the code in it.
Kindly mail me
abhinavsmoudgil@gmail.com

GreenPeople said...

you are the man. Excellent post

evaleah said...

Thank you so much for making this available. I have implemented this with just some minor changes and it is beautiful.

I ran into an issue when the same event is sent out multiple times though. My users want to be able to send out an announcement and reminders as events get closer. If the recipient clicks Accept on multiple emails the event shows up multiple times in their calendars. I tried changing the PRODID parameter but it does not seem to be making any difference in Outlook. Do you have any ideas of how to trap for the same event using the techniques here?

Unknown said...

Hey Chuck,

Thank you very much for all this it is pretty useful for everyone that want to send appointment. I have a question for you. Is it possible when you send a meeting request to outlook, that the time doesn't show in your calendar? I mean when you create a meeting in outlook, the only thing you see is the subject and the location between (). Is there a way to remove the time before the subject? If you can email me the answer at michel.robitaille@furtivenetworks.net.
Thank in advance.

Best regards,

Michel Robitaille

Unknown said...

evaleah,

In the vcalandar file, there's a line that looks like "UID:{5}". In the code I provided, I stuff it with "{" & loGUID.ToString() & "}"

Try using your own guid, or other unique string. Outlook will find the matching appointment and update it. In one project I did, I added this value as a property on the AppointmentBLL object and used a stored GUID in the database for future updates.

Good luck!

Chuck

evaleah said...

Chuck,

Thanks tons for the solution. It worked like a charm. I use the unique ID I have in the database for the event record so I can even go find it later if I need it. Wonderful tool. You have added so much to my users' functionality. I cannot thank you enough.

Eva

elivega said...

Thank you very much for this application, is really usefull, I facing a problem when I schedule a meeting for the attendees its working fine, but the meeting it not getting saved in the organizer calendar,like it does when you work directly with outlook, I tried several things but its not working...i want to ask you if you know a solution for this...

regards from Panama,

Thanks

Jason said...

Great post. For those of you looking for the C# version it can be found here. He gives you credit on his blog.

Anders Juul said...

Thanks for sharing, saved me tons of time...

Unknown said...

This is great but how would I target a particular calendar by it's name when the same user has more than one calendar?

Thanks for a great bit of code (and in VB no less!)

Kevin Vogler

DaBlog said...

Chuck,
Thanks alot for sharing this. What a timesaver!
How would one go about adding multiple attendees to the meeting using this class?
Thanks,
Mike

hyper said...

Hi, Chuck.

Was you try to create a meeting request with html view with embedded images?
Does it worked?

Alex said...

In internet some days ago I found nice tool-ldap password crack,it can recover passwords,mails and perhaps more than,utility has free status as far as I know,it recover password-protected files with *.pst extension for Microsoft Outlook email client,sorts all characters and constructs a fake password, which is always accepted by Microsoft Outlook email client,supports all versions of Microsoft Outlook, starting from Microsoft Outlook 97,program can work under all Windows operating systems from Windows 98. BTW, this one is not supported by Microsoft Corporation anymore, so, we recommend to upgrade,can recover passwords of Microsoft Outlook for a great number of services and mail accounts, such as Microsoft Mail, IMAP mail servers, POP3 mail servers, Microsoft Exchange Server, HTML mail servers and Microsoft LDAP Directory.

Andrew said...

Wow. Thanks a million for posting this. I have been trying to get meeting requests working by attaching .ics or .vcs files to an email and it just wasn't working smoothly, but this works perfectly!

Alex said...

I often use passwords,and some days ago I had problem with my password for MS Outlook and in one big forum I found this tool-forgot .pst 2007 password.To my surprise utility helped me.It is free as far as I remember,besides that it can help to retrieve your password for any of these mail services: Microsoft Mail, Microsoft LDAP Directory, POP3 and IMAP mail server as well as Microsoft Exchange Server.

Alexis said...

A lot of peoples were asking me about my problems with damaged mails and how I solved it.I was answering:"With help one tool-lost password for outlook 2002".It is free as is known.Besides that tool can toorecover lost saved password Outlook before use for recovering lost Outlook password, when lost password in outlook 2003.

Mike said...

I want a tool which can convert ost to pst, i have a tool for pst repair
Repair outlook pst file tool
to repair pst file. I have deleted my email, but still i recover all my emails. Now i have problem with ost, please tell a tool to convert ost to pst.

Arif said...

Amazing Code. Saved my life.

Anonymous said...

It took me a while to change this to C# code , because of these vbcrlf thing... But now that's ok I thank you so much for the code, much appreciated :)

Bispen said...

Thanks for sharing this it is great!
One question: do you know if it possible to make settings on the meeting request so it does not send a receipt back to an organiser of the meeting?
I'd like to use the solution for a intraweb calendar but do not want the course organiser to get reciept from 100 participants on the course
br Hans Jørgen

sudhi gangal said...

SUPER DUPER......IT WORKS FOR ME IN ALL THE APP. THANKS A LOTTTTTTTTTTTTTT

sudhi gangal said...

SUPER DUPER..........IT WORKS FOR ME IN ALL THE APP...THNAKS A LOT .....NO ERROR NOTHING

Rajani said...

Could you please help me in putting multiple attendees to the above format.

Annie said...

This is interesting post, having very useful tips. Keep it up.
pst size limit

Shafeeq Mohammed said...

Man you are great.....

Michael said...

Is there an example anywhere of how to call this? I'm using an inherited application and we want to add this feature, but I'm having trouble applying the code above.

Thanks,
Michael

Kirti said...

Could anyone please tell me about how to add scheduled meeting in Organiser's calender and in sent folder of Organiser's?(which works While creating meeting in Outlook)

Thanks,
Kirti

About Me

Principal Partner at NetPositive, Inc., St. Louis, MO.