I am creating a macro to send personalised business communications from a shared mailbox.
Below is the code:
Sub EnhancedMailMergeToEmail()
' Macro created by Imnoss Ltd
' Please share freely while retaining attribution
' Last Updated 2021-11-06
' REFERENCES REQUIRED!
' This Macro requires you to add the following libraries:
' "Microsoft Outlook xx.x Object Library" (replace xx.x with version number) and "Microsoft Scripting Runtime"
' To add them, use the "Tools" menu and select "References". Tick the check boxes next to the two libraries and press OK.
' declare variables
Dim outlookApp As Outlook.Application
Dim outlookMail As Outlook.MailItem
Dim outlookAccount As Outlook.Account
Dim fso As FileSystemObject
Dim f As Object
Dim attachFile As File
Dim mm As MailMerge
Dim df As MailMergeDataField
Dim singleDoc As Document
Dim mailBody As String
Dim lastRecordNum As Long
Dim recordCount As Long
Dim sendFlag As Boolean
Dim validRowFlag As Boolean
Dim tempFileName As String
Dim tempFolderName As String
Dim fieldName As String
Dim inputDate As Date
' identify the mail merge of the active document
Set mm = ActiveDocument.MailMerge
' check for the mail merge state being that of a mail merge ready to go
If mm.State <> wdMainAndDataSource Then
If MsgBox("Mailmerge not set up for active document - cannot perform mailmerge. Macro will exit.", vbOKOnly + vbCritical, "Error") = vbOK Then Exit Sub
End If
' set lastRecordNum to the number of the last active record (reached using wdLastRecord
mm.DataSource.ActiveRecord = wdLastRecord
lastRecordNum = mm.DataSource.ActiveRecord
' if the lastRecordNum is less than 50 we assume some may have been deselected so we count only the active records
' counting more than 50 records takes too long
If lastRecordNum < 50 Then
mm.DataSource.ActiveRecord = wdFirstRecord
recordCount = 0
Do While True
' run through the fields to check if a valid email address is provided in any of the "to", "cc" or "bcc" fields (valid address = contains an "#")
' also detect if the row is marked to be ignored
validRowFlag = False
For Each df In mm.DataSource.DataFields
' clean up the provided field name by running through the name letter by letter and adding only letters to the variable fieldName
fieldName = ""
For i = 1 To Len(df.Name)
Select Case Asc(LCase(Mid(df.Name, i, 1)))
Case 97 To 122
fieldName = fieldName & LCase(Mid(df.Name, i, 1))
End Select
Next i
Select Case fieldName
Case "ignore"
Select Case LCase(df.Value)
Case "true", "yes", "y", "ignore"
validRowFlag = False
Exit For
End Select
Case "to", "cc", "bcc"
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then
validRowFlag = True
End If
End Select
Next
If validRowFlag Then
recordCount = recordCount + 1
End If
If mm.DataSource.ActiveRecord = lastRecordNum Then
Exit Do
Else
mm.DataSource.ActiveRecord = wdNextRecord
End If
Loop
Else
recordCount = lastRecordNum
End If
If recordCount = 0 Then
If MsgBox("Cannot find any active / valid / not to be ignored records. Macro will Exit", vbOKOnly + vbCritical, "Error") = vbOK Then Exit Sub
End If
' Give the user an opportunity to abort, and also the option to save the emails in drafts, or send immediately
Select Case MsgBox("MailMerge to email will proceed for " & IIf(recordCount < 50, recordCount & " active", recordCount) & " records." _
+ Chr(10) + Chr(10) + _
"Click 'Yes' to send the emails immediatly, 'No' to save the emails in draft, and 'Cancel' to abort.", _
vbYesNoCancel + vbDefaultButton2 + vbQuestion, "Send Emails")
Case vbCancel
Exit Sub
Case vbYes
sendFlag = True
Case Else
sendFlag = False
End Select
' set variables
' outlookApp is used to control outlook to send an email
' fso is used to read the HTML file with the email content
Set outlookApp = New Outlook.Application
Set fso = New FileSystemObject
' we need to use a temporary file to store the html generated by mail merge
' fso.GetTempName creates a name with the extension tmp. We remove this
' because image files are stored in a folder with the name without the extension and with "_files" at the end
tempFileName = Replace(fso.GetTempName, ".tmp", "")
mm.DataSource.ActiveRecord = wdFirstRecord
recordCount = 0
' loop through all the records
Do While lastRecordNum > 0
' run through the fields to check if a valid email address is provided in any of the "to", "cc" or "bcc" fields (valid address = contains an "#")
' also detect if the row is marked to be ignored
validRowFlag = False
For Each df In mm.DataSource.DataFields
' clean up the provided field name by running through the name letter by letter and adding only letters to the variable fieldName
fieldName = ""
For i = 1 To Len(df.Name)
Select Case Asc(LCase(Mid(df.Name, i, 1)))
Case 97 To 122
fieldName = fieldName & LCase(Mid(df.Name, i, 1))
End Select
Next i
Select Case fieldName
Case "ignore"
Select Case LCase(df.Value)
Case "true", "yes", "y", "ignore"
validRowFlag = False
Exit For
End Select
Case "to", "cc", "bcc"
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then
validRowFlag = True
End If
End Select
Next
' only create an email if there is a valid addressa and the row is not marked as to be ignored
If validRowFlag Then
' use mailmerge to create a new document for one record (defined by mm.DataSource.ActiveRecord)
mm.Destination = wdSendToNewDocument
mm.DataSource.FirstRecord = mm.DataSource.ActiveRecord
mm.DataSource.LastRecord = mm.DataSource.ActiveRecord
mm.Execute Pause:=False
' save the generated doc as a html file in the temp directory
Set singleDoc = ActiveDocument
singleDoc.SaveAs2 FileName:=Environ("Temp") & Application.PathSeparator & tempFileName & ".tmp", FileFormat:=wdFormatFilteredHTML
singleDoc.Close SaveChanges:=wdDoNotSaveChanges
Set singleDoc = Nothing
' read the html from the temp directory using fso
mailBody = fso.OpenTextFile(Environ("Temp") & Application.PathSeparator & tempFileName & ".tmp", 1).ReadAll
' create a new email message in outlook
Set outlookMail = outlookApp.CreateItem(olMailItem)
' display the email so that any images display correctly
outlookMail.Display
' clear the content of the email and remove all attachments (i.e. clear the signature and any images in the signature)
outlookMail.HTMLBody = ""
Do While outlookMail.Attachments.Count > 0
outlookMail.Attachments.Remove 1
Loop
' ensure formatting is HTML
outlookMail.BodyFormat = olFormatHTML
' if the html contains images, then they will be stored in a directory called
' tempFileName followed by the _files in the local language (e.g. _bestanden in Dutch)
' so we need to find the directory, and the loop through each of the files
' checking to see if the files are included in the email as an image (i.e. as 'src="..."')
' if the image is included then the image is attached to the email as a hidden attachment
' and the image path is updated to point to the attached image
' try and find the temporary folder name which would contain any images
tempFolderName = ""
For Each f In fso.GetFolder(Environ("Temp")).SubFolders
If Left(f.Name, Len(tempFileName) + 1) = tempFileName & "_" Then
tempFolderName = f.Name
Exit For
End If
Next
' if the folder has been found, iterate through the files
If tempFolderName <> "" Then
For Each attachFile In fso.GetFolder(Environ("Temp") & Application.PathSeparator & tempFolderName).Files
If InStr(1, mailBody, "src=""" & tempFolderName & "/" & attachFile.Name & """", vbBinaryCompare) > 0 Then
outlookMail.Attachments.Add attachFile.Path, 1, 0
mailBody = Replace(mailBody, "src=""" & tempFolderName & "/" & attachFile.Name & """", "src=""cid:" & attachFile.Name & """")
End If
Next
End If
' add the mail body from the html created via mailmerge and updated for the newly attached images
outlookMail.HTMLBody = mailBody
' run through all the fields in the mail merge data, when an email field is identified add the data to the appropriate field
For Each df In mm.DataSource.DataFields
' first check for the field being populated for the active record (row), only check if there is data provided
If Trim(df.Value) <> "" Then
' try matching the field name to accepted field names
' the field name is cleaned up by running through the name letter by letter and adding only letters to the variable fieldName
fieldName = ""
For i = 1 To Len(df.Name)
Select Case Asc(LCase(Mid(df.Name, i, 1)))
Case 97 To 122
fieldName = fieldName & LCase(Mid(df.Name, i, 1))
End Select
Next i
Select Case fieldName
Case "to"
' add in the to address or addresses as they are presented in the data, multiple address should be separated by a semicolon
outlookMail.To = outlookMail.To & ";" & df.Value
Case "cc"
' add in the cc address or addresses as they are presented in the data, multiple address should be separated by a semicolon
outlookMail.CC = outlookMail.CC & ";" & df.Value
Case "bcc"
' add in the bcc address or addresses as they are presented in the data, multiple address should be separated by a semicolon
outlookMail.BCC = outlookMail.BCC & ";" & df.Value
Case "subject"
' add in the subject as it is presented in the data
outlookMail.Subject = df.Value
Case "importance"
' change the importance, accepted input values are "high", "normal", and "low" (not case sensitive)
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically "Normal", but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "high"
outlookMail.Importance = olImportanceHigh
Case "normal"
outlookMail.Importance = olImportanceNormal
Case "low"
outlookMail.Importance = olImportanceLow
End Select
Case "sensitivity"
' change the sensitivity, accepted input values are "confidential", "personal", "private", or "normal" (not case sensitive)
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically "Normal", but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "confidential"
outlookMail.Sensitivity = olConfidential
Case "personal"
outlookMail.Sensitivity = olPersonal
Case "private"
outlookMail.Sensitivity = olPrivate
Case "normal"
outlookMail.Sensitivity = olNormal
End Select
Case "readreceipt"
' request or do not request a read receipt
' if the field contains a boolean TRUE, or any form of "true"/"yes"/"y" (case insensitive) then request a read receipt
' if the field contains a boolean FALSE, or any form of "false"/"no"/"n" (case insensitive) then do not request a read receipt
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically to not request a read receipt, but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "true", "yes", "y"
outlookMail.ReadReceiptRequested = True
Case "false", "no", "n"
outlookMail.ReadReceiptRequested = False
End Select
Case "deliveryreceipt"
' request or do not request a delivery report
' if the field contains a boolean TRUE, or any form of "true"/"yes"/"y" (case insensitive) then request a delivery report
' if the field contains a boolean FALSE, or any form of "false"/"no"/"n" (case insensitive) then do not request a delivery report
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically to not request a delivery report, but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "true", "yes", "y"
outlookMail.OriginatorDeliveryReportRequested = True
Case "false", "no", "n"
outlookMail.OriginatorDeliveryReportRequested = False
End Select
Case "deliverytime"
' add in a delivery time (delay delivery)
' checks for the field containin a value or something which looks like a date and/or time
' if a datetime is provided, and that datetime is in the future then the delay is added to that datetime
' if a date is provided, and that date is in the future then the delay is added to midnight at the start of the provided date
' if a time is provided then the next instance of that time will be used to define the delay (so email could be sent "tomorrow" if time already passed)
' if no data, invalid data, or a date/datetime in the past is added then no delivery delay is added
If (IsNumeric(df.Value) Or IsDate(df.Value)) Then
' A date passed from an Excel table through mail merge will be formatted in US format ("m/d/yyyy"), but the function CDate
' uses the local format, e.g. ("d/m/yyyy"). CDate is nice enough to recognise (and not error) when fed a date with the day > 12,
' so both "15/1/2021" and "1/15/2021" will produce the correct date output (15 January 2021).
' The next couple of lines test for whether the date is the wrong way round and flips the month and day if needed
' A date is believed to be wrong if both month and day are 12 or lower, if CDate parses the date 1/2/2020 as 1 February 2020
' and finally if the raw input from Excel is a date string (and not a number, which would be valid)
inputDate = CDate(df.Value)
If Day(inputDate) <= 12 And Month(inputDate) <= 12 And Month(CDate("1/2/2020")) = 2 And _
InStr(1, df.Value, Format(inputDate, "d/m/yyyy")) = 1 Then
inputDate = DateSerial(Year(inputDate), Day(inputDate), Month(inputDate)) + TimeSerial(Hour(inputDate), Minute(inputDate), Second(inputDate))
End If
If inputDate < Now() - Date Then ' time only, time is in the past so set time for "tomorrow"
outlookMail.DeferredDeliveryTime = Date + 1 + inputDate
ElseIf inputDate < 1 Then ' time only, time is in the future so set time for "today"
outlookMail.DeferredDeliveryTime = Date + inputDate
ElseIf inputDate > Now() Then ' date or datetime in the future
outlookMail.DeferredDeliveryTime = inputDate
End If
Debug.Print df.Value, outlookMail.DeferredDeliveryTime
End If
Case "account"
' select the account from which the email is to be sent
' the account is identified by its full email address
' to identify the account, the code cycles through all the accounts available and selects a match
' if no data, or a non-matching email address is provided, then the default account is used
' note! not the same as send as - see below
For Each outlookAccount In outlookApp.Session.Accounts
If outlookAccount.SmtpAddress = df.Value Then
outlookMail.SendUsingAccount = outlookAccount
Exit For
End If
Next
Case "sendas"
' add in an address to send as or send on behalf of
' only added if a valid email address
' if the account does not have permissions, the email will be created but will be rejected by the Exchange server if sent
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then outlookMail.SentOnBehalfOfName = df.Value
Case "replyto"
' add in an address to reply to
' only added if a valid email address
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then outlookMail.ReplyRecipients.Add (df.Value)
Case "attachment"
' add the attachment
outlookMail.Attachments.Add df.Value
End Select ' end test for the field names
End If ' end check for the data value being blank
Next df ' move on to the next record
' check the send flag and send or save
If sendFlag Then
outlookMail.Send
Else
outlookMail.Close (olSave)
End If
Set outlookMail = Nothing
Else
recordCount = recordCount + 1 ' keep a tally of skipped records using recordCount
End If ' end the test for whether a valid address is presented in the data
' test if we have just created a document for the last record, if so we set lastRecordNum to zero to indicate that the loop should end, otherwise go to the next active record
If mm.DataSource.ActiveRecord >= lastRecordNum Then
lastRecordNum = 0
Else
mm.DataSource.ActiveRecord = wdNextRecord
End If
Loop
End Sub
I would need to send from the shared mailbox but I can't use outlookMail.SendUsingAccount = outlookAccount because the mail is not set as a second account but is linked to my personal one.
I can't use outlookMail.SentOnBehalfOfName either because I have permissions to send as and not on behalf.
Isn't there another method?
Thank you in advance
There is no other way using the Outlook object model.
Related
I would like to have a macro that I can hit, it reads the email subject line 03100-001-01 and it then saves in that directory on my computer. I just have no idea where to start.
I have no tried anything at this stage
You can use the Subject property of Outlook items to get the subject string. Then you can use the InStr function which returns a long (number) specifying the position of the first occurrence of one string within another. For example:
Dim SearchString, SearchChar, MyPos
SearchString ="XXpXXpXXPXXP" ' String to search in.
SearchChar = "P" ' Search for "P".
' A textual comparison starting at position 4. Returns 6.
MyPos = Instr(4, SearchString, SearchChar, 1)
' A binary comparison starting at position 1. Returns 9.
MyPos = Instr(1, SearchString, SearchChar, 0)
' Comparison is binary by default (last argument is omitted).
MyPos = Instr(SearchString, SearchChar) ' Returns 9.
MyPos = Instr(1, SearchString, "W") ' Returns 0.
Finally, to save the item you need to use the MailItem.SaveAs method which saves the Microsoft Outlook item to the specified path and in the format of the specified file type. If the file type is not specified, the MSG format (.msg) is used.
Sub SaveAsMsg()
Dim myItem As Outlook.Inspector
Dim objItem As Object
Set myItem = Application.ActiveInspector
If Not TypeName(myItem) = "Nothing" Then
Set objItem = myItem.CurrentItem
strname = objItem.Subject
'Prompt the user for confirmation
Dim strPrompt As String
strPrompt = "Are you sure you want to save the item? " & _
"If a file with the same name already exists, " & _
"it will be overwritten with this copy of the file."
If MsgBox(strPrompt, vbYesNo + vbQuestion) = vbYes Then
objItem.SaveAs Environ("HOMEPATH") & "\My Documents\" & strname & ".msg", olMSG
End If
Else
MsgBox "There is no current active inspector."
End If
End Sub
But I'd recommend starting from the following articles to build a basic understanding how VBA macros work:
Getting started with VBA in Office
Using Visual Basic for Applications in Outlook
I check a specific folder to see if an email exists with today's date as the received date.
The code below loops through the emails and sends me an email message if an email exists with today's date as the received date.
I want it to also send me an email if there are zero results.
The closest I got was to get six results all indicating the mail item does not exist (since there were 6 messages with dates not equal to today).
For Each olMail In OlItems
'Cond #1 Received today?
If (Now() - olMail.ReceivedTime < 1) Then
'...Cond #2 ...and has specific string
If (InStr(1, olMail.Body, "L_Remittance_YYYYMMDD", 1) > 0) Then
'......'Cond #3 and Has attachment?
If olMail.Attachments.Count > 0 Then
'..Cond 4 ...and has data in results [ "0" evaluates to string not existing ] ?
If (InStr(1, olMail.Body, "No Data Available", 1) = 0) Then
'Cond 1-4 all true then save to drop location
For j = 1 To olMail.Attachments.Count
olMail.Attachments.Item(j).SaveAsFile strFolder &
olMail.Attachments.Item(j).FileName
''Loop through files in folder ie For Each o1Mail in OlItems
Next j
'Cond 4
End If
'Cond 3
End If
'Cond 2
End If
''Cond #1
End If
Next
'' If (Now() - olMail.ReceivedTime < 1) = "" Then
'' Call FailFile
'' Else
''Exit Sub
''End If
Set OlFolder = Nothing
Set OlItems = Nothing
Set olMail = Nothing
Set OlApp = Nothing
''NO DATA IN FILE AS PER EMAIL BODY
aFile = "H:\TEST_DROP\FileName_" & CurrentDate & ".csv"
If Len(Dir$(aFile)) = 0 Then
Call NoData
End If
''YES TO DATA IN FILE
aFile = "H:\TEST_DROP\Metlife_Remittance_Berkadia_" & CurrentDate & ".csv"
If Len(Dir$(aFile)) > 0 Then
Call Data
End If
End Sub
It looks like your first condition tracks the criteria you are looking for.
Create Boolean variable rec_today
Set rec_today = FALSE
Loop through all emails
Switch rec_today to TRUE ONLY IF your first condition is met
Check condition of rec_today at the end of the loop.
Notice that if your first criteria is never met (email received today), rec_today will never be swapped to TRUE. Therefore, your actions should be based around the outcome of this variable.
Dim rec_today As Boolean
rec_today = False '<---START AT FALSE
For Each olMail In OlItems
If (Now() - olMail.ReceivedTime < 1) Then '<--- FLIP TO TRUE HERE
rec_today = True
If (InStr(1, olMail.Body, "L_Remittance_YYYYMMDD", 1) > 0) Then
If olMail.Attachments.Count > 0 Then
If (InStr(1, olMail.Body, "No Data Available", 1) = 0) Then
For j = 1 To olMail.Attachments.Count
olMail.Attachments.Item(j).SaveAsFile strFolder & olMail.Attachments.Item(j).Filename
Next j
End If
End If
End If
End If
Next
If rec_today = TRUE Then
'Code here that should run if email was found from today
Else
'Code here that should run if NO EMAIL was found from today
'i.e. rec_today = FALSE
End If
End Sub
I want to create a MACRO that can export the specific shared task list from MS outlook in to excel, So far I am only able to export task that is in the to do list but still trying to figure how to export shared task list.
Below is the snap shot for reference.
It would be great help if anyone can suggest the possible way to pull the "RTR MEC" report instead of To-do list.
Here is the code I have -
Sub ExportTasks()
' ABOUT
' Exports tasks from Outlook into an excel sheet saved to the desktop. This sheet also includes task delegator and owner (which is not included in the Outlook export wizard)
Dim Ns As Outlook.NameSpace
Set Ns = Application.GetNamespace("MAPI")
Set Items = Ns.GetDefaultFolder(olFolderTasks).Items
Const SCRIPT_NAME = "Export Tasks to Excel"
Dim olkTsk As Object, _
excApp As Object, _
excWkb As Object, _
excWks As Object, _
lngRow As Long, _
lngCnt As Long, _
strFilename As String
'USER INPUT FOR FILE NAME
strFilename = InputBox("Enter a filename. This will be saved on your desktop.", "Input Required")
If strFilename = "" Then
MsgBox "The filename is blank. Export aborted.", vbInformation + vbOKOnly
Else
MsgBox "This may take a few minutes,. Outlook will be unresponsive until this process is complete. Press okay to begin", vbOKOnly, "Information"
' CREATE EXCEL APP AND WRITE COLUMN HEADERS
' Column headers kept the same as the export wizard for compatibility.
Set excApp = CreateObject("Excel.Application")
Set excWkb = excApp.Workbooks.Add()
Set excWks = excWkb.ActiveSheet
With excWks
.Cells(1, 1) = "Subject"
.Cells(1, 2) = "StartDate"
.Cells(1, 3) = "DueDate"
End With
lngRow = 2
'DATE FILTER USING RESTRICT METHOD
'Restrict method chosen since it will be faster on computers with lots of task entries.
'FILTER ATTEMPT 1
' This code works using the restrict method, but dates are hard coded. Excludes tasks with no date set. Date format seems to default to MM/DD/YYYY
strQuery = "[DueDate] >= '11/11/2016' AND [DueDate] <= 'NOW'"
Set OlkList = Ns.GetDefaultFolder(olFolderTasks).Items.Restrict(strQuery)
'FILTER ATTEMPT 2
'Does not seem to work. Need the ability for the user to be able to specify start and end dates.
'Dim strStart As Date
'Dim strEnd As Date
'strStart = InputBox("Enter a start date using the following format MM/DD/YYYY", "Input Required")
'strEnd = InputBox("Enter a due date using the following format MM/DD/YYYY", "Input Required")
'strQuery = "[DueDate] >= 'strStart' AND [DueDate] <= 'strEnd'"
'Set OlkList = Ns.GetDefaultFolder(olFolderTasks).Items.Restrict(strQuery)
' EXPORT TASKS TO EXCEL SHEET CREATED WITH DATE RANGES SPECIFIED
For Each olkTsk In OlkList
excWks.Cells(lngRow, 1) = olkTsk.Subject
excWks.Cells(lngRow, 2) = olkTsk.StartDate
excWks.Cells(lngRow, 3) = olkTsk.DueDate
lngRow = lngRow + 1
lngCnt = lngCnt + 1
Next
Set olkTsk = Nothing
'SAVE SHEET ON DESKTOP USING THE NAME SPECIFIED BY THE USER
excWkb.SaveAs CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\" & strFilename
excWkb.Close
MsgBox "Completed! A total of " & lngCnt & " tasks were exported.", vbInformation + vbOKOnly, "PROCESS COMPLETED "
End If
Set excWks = Nothing
Set excWkb = Nothing
Set excApp = Nothing
End Sub
If you know the email address or user name of the person who shared that Tasks folder, you can use the NameSpace.GetSharedDefaultFolder method to retrieve the folder. Otherwise, you can get it from the NavigationFolder.Folder property via the TasksModule -> NavigationGroups.
I have created an excel addin but I want to find a way to get the debugging information to myself. My users are very far and run on different OS's and Office versions. I have tried emailing but the outlook security warnings are giving me a hard time and the CDO mail object needs smtp details which will be different for all my users. Is there something like general smtp settings that will always work ? Any other suggestion are welcome.
Any help will be appreciated.
there is code and documentation available on the web for sending mail using SMTP from Excel in VBA:
Chip Pearson: Sending Email with VBA and Collaboration Data Objects
The code requires a reference to Microsoft CDO for Windows 2000 Library
. The typical file location of this file is C:\Windows\system32\cdosys.dll. The GUID of this component is {CD000000-8B95-11D1-82DB-00C04FB1625D}, with Major = 1 and Minor = 0.
' COPIED FROM Chip Pearson Website: http://www.cpearson.com/excel/Email.aspx
'
Function SendEMail(Subject As String, _
FromAddress As String, _
ToAddress As String, _
MailBody As String, _
SMTP_Server As String, _
BodyFileName As String, _
Optional Attachments As Variant = Empty) As Boolean
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SendEmail Function
' By Chip Pearson, chip#cpearson.com www.cpearson.com 28-June-2012
'
' This function sends an email to the specified user.
' Parameters:
' Subject: The subject of the email.
' FromAddress: The sender's email address
' ToAddress: The recipient's email address or addresses.
' MailBody: The body of the email.
' SMTP_Server: The SMTP-Server name for outgoing mail.
' BodyFileName: A text file containing the body of the email.
' Attachments A single file name or an array of file names to
' attach to the message. The files must exist.
' Return Value:
' True if successful.
' False if failure.
'
' Subject may not be an empty string.
' FromAddress must be a valid email address.
' ToAddress must be a valid email address. To send to multiple recipients,
' use a semi-colon to separate the individual addresses. If there is a
' failure in one address, processing terminates and messages are not
' send to the rest of the recipients.
' If MailBody is vbNullString and BodyFileName is an existing text file, the content
' of the file named by BodyFileName is put into the body of the email. If
' BodyFileName does not exist, the function returns False. The content of
' the message body is created by a line-by-line import from BodyFileName.
' If MailBody is not vbNullString, then BodyFileName is ignored and the body
' is not created from the file.
' SMTP_Server must be a valid accessable SMTP server name.
' If both MailBody and BodyFileName are vbNullString, the mail message is
' sent with no body content.
' Attachments can be either a single file name as a String or an array of
' file names. If an attachment file does not exist, it is skipped but
' does not cause the procedure to terminate.
'
' If you want to send ThisWorkbook as an attachment to the message, use code
' like the following:
' ThisWorkbook.Save
' ThisWorkbook.ChangeFileAccess xlReadOnly
' B = SendEmail( _
' ... parameters ...
' Attachments:=ThisWorkbook.FullName)
' ThisWorkbook.ChangeFileAccess xlReadWrite
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Required References:
' --------------------
' Microsoft CDO for Windows 2000 Library
' Typical File Location: C:\Windows\system32\cdosys.dll
' GUID: {CD000000-8B95-11D1-82DB-00C04FB1625D}
' Major: 1 Minor: 0
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim MailMessage As CDO.Message
Dim N As Long
Dim FNum As Integer
Dim S As String
Dim Body As String
Dim Recips() As String
Dim Recip As String
Dim NRecip As Long
' ensure required parameters are present and valid.
If Len(Trim(Subject)) = 0 Then
SendEMail = False
Exit Function
End If
If Len(Trim(FromAddress)) = 0 Then
SendEMail = False
Exit Function
End If
If Len(Trim(SMTP_Server)) = 0 Then
SendEMail = False
Exit Function
End If
' Clean up the addresses
Recip = Replace(ToAddress, Space(1), vbNullString)
If Right(Recip, 1) = ";" Then
Recip = Left(Recip, Len(Recip) - 1)
End If
Recips = Split(Recip, ";")
For NRecip = LBound(Recips) To UBound(Recips)
On Error Resume Next
' Create a CDO Message object.
Set MailMessage = CreateObject("CDO.Message")
If Err.Number <> 0 Then
SendEMail = False
Exit Function
End If
Err.Clear
On Error GoTo 0
With MailMessage
.Subject = Subject
.From = FromAddress
.To = Recips(NRecip)
If MailBody <> vbNullString Then
.TextBody = MailBody
Else
If BodyFileName <> vbNullString Then
If Dir(BodyFileName, vbNormal) <> vbNullString Then
' import the text of the body from file BodyFileName
FNum = FreeFile
S = vbNullString
Body = vbNullString
Open BodyFileName For Input Access Read As #FNum
Do Until EOF(FNum)
Line Input #FNum, S
Body = Body & vbNewLine & S
Loop
Close #FNum
.TextBody = Body
Else
' BodyFileName not found.
SendEMail = False
Exit Function
End If
End If ' MailBody and BodyFileName are both vbNullString.
End If
If IsArray(Attachments) = True Then
' attach all the files in the array.
For N = LBound(Attachments) To UBound(Attachments)
' ensure the attachment file exists and attach it.
If Attachments(N) <> vbNullString Then
If Dir(Attachments(N), vbNormal) <> vbNullString Then
.AddAttachment Attachments(N)
End If
End If
Next N
Else
' ensure the file exists and if so, attach it to the message.
If Attachments <> vbNullString Then
If Dir(CStr(Attachments), vbNormal) <> vbNullString Then
.AddAttachment Attachments
End If
End If
End If
With .Configuration.Fields
' set up the SMTP configuration
.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = SMTP_Server
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
.Update
End With
On Error Resume Next
Err.Clear
' Send the message
.Send
If Err.Number = 0 Then
SendEMail = True
Else
SendEMail = False
Exit Function
End If
End With
Next NRecip
SendEMail = True
End Function
Another option is to use MAPI?
This is from the MS Knowledge Base:
Dim objSession As Object
Dim objMessage As Object
Dim objRecipient As Object
'Create the Session Object.
Set objSession = CreateObject("mapi.session")
'Logon using the session object.
'Specify a valid profile name if you want to.
'Avoid the logon dialog box.
objSession.Logon profileName:="MS Exchange Settings"
'Add a new message object to the OutBox.
Set objMessage = objSession.Outbox.Messages.Add
'Set the properties of the message object.
objMessage.subject = "This is a test."
objMessage.Text = "This is the message text."
'Add a recipient object to the objMessage.Recipients collection.
Set objRecipient = objMessage.Recipients.Add
'Set the properties of the recipient object.
objRecipient.Name = "John Doe" '<---Replace this with a valid
'display name or e-mail alias
'Type can be ActMsgTo, mapiTo, or CdoTo for different CDO versions;
'they all have a constant value of 1.
objRecipient.Type = mapiTo
objRecipient.Resolve
'Send the message.
objMessage.Send showDialog:=False
MsgBox "Message sent successfully!"
'Logoff using the session object.
objSession.Logoff
I'm an intern in Europe working at a hospital. My daily job is to find replacements for a nurse or doctor or surgeon when one is needed. To do this, I receive a request from a certain department, in the form of an excel spreadsheet with 4 different attributes which determines the time, department and specific type of personnel required.
From that info I look into a fixed database which is also based in an excel spreadsheet, for someone who matches the requirements.
After I send an email/sms or call the department head to get an approval, in which the reply is almost always yes.
Once I get the confirmation, I send the replacement's information to the department which requires the replacement and then my job is done. I do about 150 of these requests a day and if I can write a program for this, I would be able to save the hospital a lot of tax payers money, as they employ 3 other people to do this job.
Therefore, my question:
What is the best language to write this program in?
Would you recommend a scripting language which may make it easier to access files and send emails? or would that we too weak for this task?
The requirements for the language are to do the following:
Access excel spreadsheets
Read the spreadsheet and copy the values from an array of cells
Find a value in the spreadsheet
send emails with the values I obtained in my excel spreadsheet search?
read an email and if value is = to YES, do ... else do ...
finally, send an email with xxxxx information to xxx person
If I were using my mac, I would have gone to a scripting language like applescript combined with automator to access and read the excel files and send emails/sms's.
Thanks for you help in advance.
The code below is a long way from a complete solution. Its purpose is to start you thinking about how your system will function.
Looking to the future, I envisage the need for a text file which I have named HumanActionRequired.txt. The tenth line of code is a constant that specifies the folder in which this file will be created. You must replace "C:\DataArea\Play" with the name of a folder on your system. You may wish to rename the file: see sixth line.
Although I envisage this file to be the destination of error messages, I have used it here to list details of the messages in InBox. I have only output a small selection of the available properties but it should get you thinking about what is possible.
The code below belongs in a Module within OutLook:
Open Outlook.
Select Tools, Macro and Security. You will need to set the security level to Medium. Later you can discuss getting trusted status for your macro with your IT department but this will do for now.
Select Tools, Macro and Visual Basic Editor or click Alt+F11.
You will probably see the Project Explorer down the left (Control+R to display if not). If you have never created an Outlook macro, the area to the right will be grey.
Select Insert, Module. The grey area will go white with the code area above and the Immediate window below.
Copy the code below into the code area.
Position the cursor within the macro LocateInterestingEmails() and click F5. You will be warned that a macro is trying to access your emails. Tick Allow access for and select a time limit then click Yes. The macro will write selected properties of the emails in Inbox to the file HumanActionRequired.txt.
Option Explicit
Sub LocateInterestingEmails()
Dim ErrorDescription As String
Dim ErrorNumber As Long
Static ErrorCount As Integer
Const FileCrnt As String = "HumanActionRequired.txt"
Dim FolderTgt As MAPIFolder
Dim InxAttachCrnt As Long
Dim InxItemCrnt As Long
Dim OutputFileNum As Long
Const PathCrnt As String = "C:\DataArea\Play"
ErrorCount = 0
OutputFileNum = 0
Restart:
' On Error GoTo CloseDown
Set FolderTgt = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileCrnt For Append Lock Write As #OutputFileNum
For InxItemCrnt = 1 To FolderTgt.Items.Count
With FolderTgt.Items.Item(InxItemCrnt)
If .Class = olMail Then
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Subject: " & .Subject
Print #OutputFileNum, "Sender: " & .SenderEmailAddress
Print #OutputFileNum, "Recipient: " & .To
Print #OutputFileNum, "Date sent: " & .SentOn
If .Attachments.Count > 0 Then
Print #OutputFileNum, "Attachments:"
For InxAttachCrnt = 1 To .Attachments.Count
Print #OutputFileNum, " " & .Attachments(InxAttachCrnt).DisplayName
Next
End If
End If
End With
Next
CloseDown:
ErrorNumber = Err.Number
ErrorDescription = Err.Description
Err.Clear
Set FolderTgt = Nothing
If ErrorNumber <> 0 Then
' Here because of an error
If OutputFileNum = 0 Then
' Output file not open
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileCrnt For Append Lock Write As #OutputFileNum
End If
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Error at " & Now()
Print #OutputFileNum, "Error number = " & ErrorNumber & _
" description = " & ErrorDescription
End If
If OutputFileNum <> 0 Then
' File open
Close OutputFileNum
OutputFileNum = 0
End If
End Sub
Version 2
This version includes the code in the first version plus:
It opens an existing workbook to which it saves information about the Excel attachments found.
It identifies attachments with an extension of xls? and saves them to disc with a name based on the date/time received and the sender's name.
It opens each saved attachment. For each worksheet in a saved attachment, it creates a row in the existing workbook containing filenames, sender name and email address, sheet name and the value of cell A1.
I do not think this code will be directly useful but it shows how to save attachments and open workbooks to read from or write to then which I believe you will need.
The only code I know to be missing is:
Move processed email to save folder.
Generate reply email.
However, more code may be necessary depending on how you want to automate the entire process.
The code below is not as neat as I would like. I do not want to add any more until you have fully understood it. I would also like a better understanding of the emails you plan to send and the desired automation of the total process.
Come back with questions on any part of the code you do not understand.
Option Explicit
Sub LocateInterestingEmails()
' I use constants to indentify columns in worksbooks because if I move the
' column I only need to update the constant to update the code. I said the
' same in a previous answer and some one responded that they preferred
' Enumerations. I use Enumerations a lot but I still prefer to use constants
' for column numbers.
Const ColSumFileNameSaved As String = "A"
Const ColSumFileNameOriginal As String = "B"
Const ColSumSenderName As String = "C"
Const ColSumSenderEmail As String = "D"
Const ColSumSheet As String = "E"
Const ColSumCellA1 As String = "F"
' You must change the value of this constant to the name of a folder on your
' computer. All file created by this macro are written to this folder.
Const PathCrnt As String = "C:\DataArea\Play"
' I suggest you change the values of these constants to
' something that you find helpful.
Const FileNameHAR As String = "HumanActionRequired.txt"
Const FileNameSummary As String = "Paolo.xls"
Dim CellValueA1 As Variant
Dim ErrorDescription As String
Dim ErrorNumber As Long
Dim FileNameReqDisplay As String
Dim FileNameReqSaved As String
Dim FolderTgt As MAPIFolder
Dim InxAttachCrnt As Long
Dim InxItemCrnt As Long
Dim InxSheet As Long
Dim OutputFileNum As Long
Dim Pos As Long
Dim ReceivedTime As Date
Dim RowSummary As Long
Dim SenderName As String
Dim SenderEmail As String
Dim SheetName As String
Dim XlApp As Excel.Application
Dim XlWkBkRequest As Excel.Workbook
Dim XlWkBkSummary As Excel.Workbook
' Ensure resource controls are null before macro does anything that can cause
' an error so error handler knows if the resource is to be released.
OutputFileNum = 0
Set XlApp = Nothing
Set XlWkBkRequest = Nothing
Set XlWkBkSummary = Nothing
' Open own copy of Excel
Set XlApp = Application.CreateObject("Excel.Application")
With XlApp
.Visible = True ' This slows your macro but helps during debugging
' Open workbook to which a summary of workbooks extracted will be written
Set XlWkBkSummary = .Workbooks.Open(PathCrnt & "\" & FileNameSummary)
With XlWkBkSummary.Worksheets("Summary")
' Set RowSummary to one more than the last currently used row
RowSummary = .Cells(.Rows.Count, ColSumFileNameSaved).End(xlUp).Row + 1
End With
End With
Restart:
' I prefer to have my error handler switched off during development so the
' macro stops on the faulty statement. If you remove the comment mark from
' the On Error statement then any error will cause the code to junp to label
' CloseDown which is at the bottom of this routine.
' On Error GoTo CloseDown
' Gain access to InBox
Set FolderTgt = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
' Open text file for output. I envisage this file being used for error
' messages but for this version of the macro I write a summary of the
' contents of the InBox to it.
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileNameHAR For Output Lock Write As #OutputFileNum
For InxItemCrnt = 1 To FolderTgt.Items.Count
With FolderTgt.Items.Item(InxItemCrnt)
If .Class = olMail Then
' Only interested in mail items. Most of the other items will be
' meeting requests.
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Subject: " & .Subject
' Currently we are within With FolderTgt.Items.Item(InxItemCrnt).
' Values from this mail item are to be written to a workbook
' for which another With will be required. Copy values to
' variables for they are accessable.
' Note: XlApp.XlWkBkSummary.Worksheets("Summary")
' .Cells(RowSummary, ColSumFileNameOriginal).Value = _
' FolderTgt.Items.Item(InxItemCrnt).Attachments(InxAttachCrnt) _
' .DisplayName
' is legal but is not very clear. Code is much clearer will full use
' of With stateents even if it means values must be copied to variable.
SenderName = .SenderName
SenderEmail = .SenderEmailAddress
ReceivedTime = .ReceivedTime
Print #OutputFileNum, "SenderName: " & SenderName
Print #OutputFileNum, "SenderAddr: " & SenderEmail
Print #OutputFileNum, "Received: " & ReceivedTime
Print #OutputFileNum, "Date sent: " & .SentOn
If .Attachments.Count > 0 Then
Print #OutputFileNum, "Attachments:"
For InxAttachCrnt = 1 To .Attachments.Count
With .Attachments(InxAttachCrnt)
' I cannot find an example for which the
' DisplayName and FileName are different
FileNameReqDisplay = .DisplayName
Print #OutputFileNum, " " & FileNameReqDisplay & "|" & .FileName
Pos = InStrRev(FileNameReqDisplay, ".")
' With ... End With and If ... End If must be properly nested.
' Within the If below I want access to the attachment and to the
' workbook. Hence the need to terminate the current With and then
' immediately start it again within the If ... End If block.
End With
If LCase(Mid(FileNameReqDisplay, Pos + 1, 3)) = "xls" Then
With .Attachments(InxAttachCrnt)
' Save the attachment with a unique name. Note this will only be
' unique if you do not save the same attachment again.
FileNameReqSaved = _
Format(ReceivedTime, "yyyymmddhhmmss") & " " & SenderName
.SaveAsFile PathCrnt & "\" & FileNameReqSaved
End With
' Open the saved attachment
Set XlWkBkRequest = _
XlApp.Workbooks.Open(PathCrnt & "\" & FileNameReqSaved)
With XlWkBkRequest
'Examine every worksheet in workbook
For InxSheet = 1 To .Worksheets.Count
With .Worksheets(InxSheet)
' Save sheet name and a sample value
SheetName = .Name
CellValueA1 = .Cells(1, 1).Value
End With
' Save information about this sheet and its workbook
With XlWkBkSummary.Worksheets("Summary")
.Cells(RowSummary, ColSumFileNameSaved).Value = _
FileNameReqSaved
.Cells(RowSummary, ColSumFileNameOriginal).Value = _
FileNameReqDisplay
.Cells(RowSummary, ColSumSenderName).Value = SenderName
.Cells(RowSummary, ColSumSenderEmail).Value = SenderEmail
.Cells(RowSummary, ColSumSheet).Value = SheetName
.Cells(RowSummary, ColSumCellA1).Value = CellValueA1
RowSummary = RowSummary + 1
End With ' XlWkBkSummary.Worksheets("Summary")
Next InxSheet
.Close SaveChanges:=False
Set XlWkBkRequest = Nothing
End With ' XlWkBkRequest
End If
Next
End If
End If
End With
Next
CloseDown:
ErrorNumber = Err.Number
ErrorDescription = Err.Description
Err.Clear
Set FolderTgt = Nothing
If ErrorNumber <> 0 Then
' Have reached here because of an error
If OutputFileNum = 0 Then
' Output file not open
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileNameHAR For Append Lock Write As #OutputFileNum
End If
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Error at " & Now()
Print #OutputFileNum, "Error number = " & ErrorNumber & _
" description = " & ErrorDescription
End If
' Release resources
If OutputFileNum <> 0 Then
' File open
Close OutputFileNum
OutputFileNum = 0
End If
If Not (XlWkBkRequest Is Nothing) Then
XlWkBkRequest.Close SaveChanges:=False
Set XlWkBkRequest = Nothing
End If
If Not (XlWkBkSummary Is Nothing) Then
XlWkBkSummary.Close SaveChanges:=True
Set XlWkBkSummary = Nothing
End If
If Not (XlApp Is Nothing) Then
XlApp.Quit
Set XlApp = Nothing
End If
End Sub