My excel spreadsheet has email a number of email addresses in column B that the email is to be sent to and addresses in column E that the email is to be cc to. in A1 I have today's date (which updates) and in B1 I have the date the email needs to be sent and in C1 I have another date 6 months later the email needs to be sent.
Here is my code:
Private Sub workbook_Open()
Dim Subject, Send_From, Send_To, cc, body As String
Dim Email_Range, cl, cc_Range, cx As Range
Dim Due_Date, Today_Date, Send_Date As Date
Today_Date = Range ("A1").Text
Due_Date = Today_Date + 7
Send_Date = Range ("B1").Text
Dim Mail_Object, Mail_Single As Variant
Set Email_Range = Range("B3:B14")
For Each cl In Email_Range
Sent_To = Send_To & ";" & cl.Value
Next
Send_To = Mid(Send_To, 2)
Set cc_Range = Range("E3:E14")
For Each cx In cc_Range
cc = cc & ";" & cx.Value
Next
cc = Mid(cc, 2)
Subject="subject"
Send_From="me#example.com"
Body="Please respond by" & "Due_Date" & "Thank You"
On Error GoTo debugs
Set Mai_Object = Create Object ("Outlook Application")
Set Mail_Single - Mail_Object, Create Item(0)
With Mail_Single
.Subject=Subject
.To=Send_To
.cc=cc
.Body=Body
.Send
End With
debugs:
If Err.Description c>"" Then Msg Box
Err.Description
End Sub
The program will send an email when I run it, but I need the program to send an email on the send date automatically (when due date=send date) and then when someone responds, take them off the email list. Repeat this until everyone has responded and then do it all again the next send date (different cell).
I have no idea where to start as I am pretty knew at this. Does anyone have any ideas?
There is a solution using Excel and Outlook utilising Outlook's task reminders, but it will send emails at the precise time only if your Outlook software is actively running at the moment a reminder event occurs. If you start Outlook and have 'missed' one or more scheduled events (because Outlook was turned off at the time), then emails corresponding with those missed scheduled events will occur shortly after Outlook starts. I'm assuming you have Outlook available as you are using Excel.
Excel Component Solution
I've created a spreadsheet that is slightly different to yours, but you can adapt as necessary. The spreadsheet has two separate sheets named "Recipients" and "Emails". The "Recipients" sheet contains a list of each email recipient along with three dates on which they should receive emails if they have not yet responded.
The "Emails" sheet contains content that is to be placed in each email:
Within the ThisWorkbook code for the spreadsheet, I added the following procedures. These are responsible for creating tasks within Outlook for which reminders will fire at particular times in the future. Each task will contain sufficient information to generate an email to a nominated recipient.
Sub CreateOutlookTask(name As String, destination As String, emailNum As Integer, sendDate As Date)
With CreateObject("Outlook.Application").CreateItem(3)
.Subject = Worksheets("Emails").Cells((emailNum + 1), "A").Value
.Role = destination
.StartDate = sendDate
.DueDate = sendDate
.ReminderTime = sendDate
.ReminderSet = True
.Body = Trim(Worksheets("Emails").Cells((emailNum + 1), "B").Value) + _
" " + name + "," + vbLf + vbLf + _
Worksheets("Emails").Cells(emailNum + 1, "C").Value + _
vbLf + vbLf + _
"This email was sent to " + destination + _
" by XYZ Corporation [add reason]."
.Categories = "AutoSend"
.Save
End With
End Sub
Sub ProcessRecipients()
Dim rowNum As Integer
Dim lastRowIdx As Integer
lastRowIdx = Worksheets("Recipients").Cells(Rows.Count, "B").End(xlUp).Row
For rowNum = 2 To lastRowIdx
With Worksheets("Recipients")
Call CreateOutlookTask(.Cells(rowNum, "A"), .Cells(rowNum, "B"), 1, .Cells(rowNum, "C"))
Call CreateOutlookTask(.Cells(rowNum, "A"), .Cells(rowNum, "B"), 2, .Cells(rowNum, "D"))
Call CreateOutlookTask(.Cells(rowNum, "A"), .Cells(rowNum, "B"), 3, .Cells(rowNum, "E"))
End With
Next
End Sub
Running the ProcessRecipients() macro within the Excel workbook will create three tasks in Outlook for each recipient listed on the "Recipients" sheet. However, the email magic won't happen until the following section is complete.
Outlook Component Solution
Two separate actions need to occur within Outlook. The first is the actual sending of emails at nominated times, and the second is looking for and processing the responses.
Automatically processing received emails is made more complicated by the fact the email address of the responder may not be the same as the email address of the originally intended recipient. By placing the intended recipient's email address within the body of the original email, it is highly likely that any response will include the original email and thus include a reference to the originally intended recipient. The automatic processing of emails looks known text within the subject heading string as well as a reference to an originally intended recipient's email address in the received email body.
The following VBA code needs to be placed in a (new) module within Outlook's VbaProject.OTM file. This code will become a rule that will be run whenever an email is received by the Outlook client.
' Create a rule that calls this macro each time an email is received.
' All tasks that are flagged with the 'AutoSend' category will be searched
' and the email destination for that task extracted from the task's 'role'
' field. If the received email explicitly refers to that email address, then
' the task will be deleted.
'
' It cannot be assumed that the sender of a response email will be the same
' email address as the email used to send the original email (i.e. it could
' have been forwarded, or simply be an alias for the actual recipient. We
' must therefore search the body of the response to look for a reference to
' the originally intended recipient email address.
'
Sub ProcessAutoEmailResponses(email As MailItem)
Dim task As Outlook.TaskItem
Dim items As Outlook.items
Dim folder As Outlook.MAPIFolder
Dim deletedTasks As String
Dim autoProcess As Boolean
autoProcess = False
Set folder = Application.Session.GetDefaultFolder(olFolderTasks)
Set items = folder.items
' If the incoming email subject contains any of the strings defined
' by an 'AutoReceive' task category subject
Set task = items.Find("[Categories] ='AutoReceive'")
Do While (Not task Is Nothing) And (autoProcess = False)
If (InStr(1, email.Subject, task.Subject) > 0) Then
autoProcess = True
End If
Set task = items.FindNext
Loop
If (autoProcess = True) Then
deletedTasks = "AutoSend Processing Triggered"
' loop through all AutoEmail categorised tasks
Set task = items.Find("[Categories] ='AutoSend'")
Do While (Not task Is Nothing)
' if the email contains a reference to the task's destination email address
If (InStr(1, email.Body, task.Role) > 0) Then
deletedTasks = deletedTasks & ", Deleted Reminder " & task.DueDate & " (" & task.Subject & ")"
' delete the task
task.Delete
End If
Set task = items.FindNext
Loop
' Insert note to indicate tasks have been deleted
email.Body = deletedTasks + vbLf + email.Body
email.Subject = "[AUTOSEND PROCESSED] " + email.Subject
email.Save
End If
End Sub
A second block of code needs to be placed in the ThisOutlookSession area of the VbaProject.OTM codebase. This code is executed whenever a reminder fires.
Note there are several ways to do this, and although I ended up not 'cancelling' the reminder window via this event handler, the BeforeReminderShow event handler is (I believe) the only way to control whether the reminder window is actually made visible as a result of a reminder firing. It might be something you wish to play with further.
Private WithEvents olRemind As Outlook.Reminders
Private Sub Application_Reminder(ByVal item As Object)
Set olRemind = Outlook.Reminders
End Sub
'
' Auto-dismiss/cancel reminders that would otherwise
' be displayed for "AutoSend" categorised items
'
Private Sub olRemind_BeforeReminderShow(Cancel As Boolean)
Dim reminderObj As Reminder
Dim item As TaskItem
For Each reminderObj In olRemind
If (reminderObj.IsVisible = True) Then
If (TypeName(reminderObj.item) = "TaskItem") Then
If reminderObj.item.Categories = "AutoSend" Then
Dim email As MailItem
Set email = Application.CreateItem(olMailItem)
email.To = reminderObj.item.Role
email.Subject = reminderObj.item.Subject
email.Body = reminderObj.item.Body
email.Send
reminderObj.item.ReminderSet = False
reminderObj.item.Save
reminderObj.Dismiss
End If
End If
End If
Next
End Sub
Important: Outlook macros are usually prohibited under the default Outlook configuration. It is best to sign your code and permit the execution of signed macros. Once your code is run once, you can nominate to 'always allow' that code to run, thereby eliminating permission problems.
This blog provides instructions for 'self-signing' your VBA project: http://www.remkoweijnen.nl/blog/2011/01/12/self-signing-word-macros/.
Final Steps
Create one or more 'tasks' within outlook (no date necessary) that contain the email subject headings as their titles and tag them with a category called 'AutoReceive'. These will control which emails are automatically processed based on the content of their email subject.
You'll now need to set-up a rule within Outlook to run the ProcessAutoEmailResponses(MailItem) method by going to Tools->Manage Rules and Alerts (or similar, depending on your Outlook version) and creating a rule that runs the method whenever a new email arrives.
As previously stated in a comment by Matt Rowland, Excel is not really the proper choice to do something like that. The easiest way to accomplish that would be probably using an SQL Server (or any other DB-Server) with the ability to send out emails.
The main problem is that you want Excel to run on a particular date / time. Yet, Excel is not running day and night on your computer. So, if Excel is not open / running on the particular date / time you want it to, then Excel cannot send out the requested emails.
Still, if the task has to be accomplish using Excel this is possible. First, you will have to make sure that you have a computer running day and night so that the computer is really on and running on the requested dates. Second, you need to setup the Windows Task (https://en.wikipedia.org/wiki/Windows_Task_Scheduler) to open Excel with the file you setup for that purpose (with the above code). To be really specific, you can even use the VBA function Application.OnTime (https://msdn.microsoft.com/en-gb/en-en/library/office/ff196165.aspx) to run your above procedure at a particular time.
Related
I receive 4 weekly emails from 3 different senders.
Emails 1 and 2 are from the same sender and can be recognized through VBA. These emails contain zip files, where each zip file has one .csv file.
Emails 3 and 4 can also be recognized by VBA and the attachments are Excel sheets (.xlsx).
I want to extract and unzip (where needed) and save these 4 files in a folder as; email1.report, email2.report etc.
Then make a copy of these 4 files in a different folder for each file and rename like; "Today's date".email1.report.csv etc.
I want to combine these steps in a single code and to replace the email1.report, email2.report etc., files without a prompt asking "do you want to replace the files? Yes, No?"
Is it possible to detect the new weekly emails and do this automatically?
The code I use to unzip and save:
Else
For Each Atmt In Item.Attachments
If Right(Atmt.FileName, 3) = "zip" Then
FileNameFolder = "C:\Users\..."
FileName = FileNameFolder & Left(Atmt.FileName, (InStr(1, Atmt.FileName, ".zip") - 1)) & ".txt"
Atmt.SaveAsFile FileName
FileNameT = FileNameFolder & Atmt.FileName
Name FileName As FileNameT
Set oApp = CreateObject("Shell.Application")
oApp.NameSpace((FileNameFolder)).CopyHere oApp.NameSpace((FileNameT)).Items
Kill FileNameT
i = i + 1
End If
Next Atmt
'item.Close
End If
I won't develop the code for your specific problem, but I recently wrote something similar. Maybe you can go from here by altering to your criteria etc.
In my case I had two e-mails incoming shortly after another, within 60 seconds. Both mails had "FP" in their subject and a .pdf-attachment. The task was to concatenate these attachments using the installed PDF24, which luckily offers a shell command for this.
This was the code, placed in the "ThisOutlookSession" of the Outlook VBA project explorer.
Public btAttachmentMails As Byte
Public dtArrivalStamp As Date
Public strPathFirstMailAttachment As String
Private WithEvents inboxItems As Outlook.Items
Private Sub Application_Startup()
Dim outlookApp As Outlook.Application
Dim objectNS As Outlook.NameSpace
Set outlookApp = Outlook.Application
Set objectNS = outlookApp.GetNamespace("MAPI")
Set inboxItems = objectNS.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub inboxItems_ItemAdd(ByVal Item As Object)
On Error GoTo ErrorHandler
Dim Msg As Outlook.MailItem
If TypeName(Item) = "MailItem" Then
Dim i As Integer
Dim strDocumentsFolder As String
strDocumentsFolder = CreateObject("WScript.Shell").SpecialFolders(16)
strPathFirstMailAttachment = strDocumentsFolder & "\attachment_mail1.pdf"
If Item.Subject Like "FP*" Then
If btAttachmentMails = 0 Then
'first mail -> save attachment and set counter to 1
btAttachmentMails = 1
dtArrivalStamp = Time
For i = 1 To Item.Attachments.Count
If InStr(Item.Attachments.Item(i).DisplayName, ".PDF") > 0 Then
Item.Attachments.Item(i).SaveAsFile strPathFirstMailAttachment
End If
Next i
ElseIf btAttachmentMails = 1 Then
Dim dtNow As Date: dtNow = Time
If TimeDiff(dtArrivalStamp, dtNow) <= 60 Then
'second mail within 60 seconds with subject containing "FP" -> save attachment and concatenate both via pdf24, then delete both files
'save attachment of second mail
Dim strPathSecondMailAttachment As String
strPathSecondMailAttachment = strDocumentsFolder & "\attachment_mail2.pdf"
For i = 1 To Item.Attachments.Count
If InStr(Item.Attachments.Item(i).DisplayName, ".PDF") > 0 Then
Item.Attachments.Item(i).SaveAsFile strPathSecondMailAttachment
End If
Next i
'concatenate pdf documents via pdf24 shell
Dim strOutputPath As String
strOutputPath = CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\" & Year(Date) & Month(Date) & Day(Date) & "_Wartungsplan_" & Replace(CStr(Time), ":", "-") & ".PDF"
Shell ("""C:\Program Files (x86)\PDF24\pdf24-DocTool.exe"" -join -profile ""default/good"" -outputFile " & strOutputPath & " " & strPathFirstMailAttachment & " " & strPathSecondMailAttachment)
'inform user
MsgBox ("Files have been successfully concatenated. You can find the combined file on your desktop.")
'reset status, delete temporary documents
btAttachmentMails = 0
If CreateObject("Scripting.FileSystemObject").fileexists(strPathFirstMailAttachment) Then Kill strPathFirstMailAttachment
If CreateObject("Scripting.FileSystemObject").fileexists(strPathSecondMailAttachment) Then Kill strPathSecondMailAttachment
Else
'second mail did not arrive within 60 seconds -> treat as first mail
'save new arrival time and overwrite old firstMailAttachment with this one
dtArrivalStamp = Time
For i = 1 To Item.Attachments.Count
If InStr(Item.Attachments.Item(i).DisplayName, ".PDF") > 0 Then
Item.Attachments.Item(i).SaveAsFile strPathFirstMailAttachment 'overwrites existing file
End If
Next i
End If
End If
End If
End If
ExitNewItem:
Exit Sub
ErrorHandler:
MsgBox Err.Number & " - " & Err.Description & " - please contact XY"
Resume ExitNewItem
End Sub
Function TimeDiff(StartTime As Date, StopTime As Date)
TimeDiff = Abs(StopTime - StartTime) * 86400
End Function
cr44sh has posted an answer while I was creating mine. He has recommended using a new item event while I have recommended using a rule. I prefer rules but you can choose which ever approach you favour.
It is impossible to fully answer your question but I believe I can give enough help for you to construct the macros you need yourself.
You say that these emails can be identified with VBA. That suggests the best approach is an Outlook rule which uses the “run a script” option where “run a script” means “run a macro”. I will discuss the rule later but first you need the macros that will be run.
You will need two macros like this:
Public Sub Type1Email(ByRef ItemCrnt As MailItem)
' Relevant code
End Sub
Public Sub Type2Email(ByRef ItemCrnt As MailItem)
' Relevant code
End Sub
I am sure you can create better names for these macros. I have read that macros to be run by a rule must be in ThisOutlookSession. In my experience, they can be in an ordinary module providing they are declared as Public. I only use ThisOutlookSession for code that has to be in that code area. If code can be in a module, that is where I place it. I suggest creating a new module which will be named Module1 or Module2. Use function key F4 to access its properties and rename it as “ModRuleMacros” or similar. Giving modules meaningful names makes it so much easier to find the code you want to look at today.
Although the aim is to create a macro to be run by a rule, you need a way of testing the macro. If you have some of these emails saved somewhere, you can activate the rule by moving one of those emails to Inbox. However, I generally find it easier to use a macro like this:
Sub TestType1Email()
Dim Exp As Explorer
Dim ItemCrnt As MailItem
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Call MsgBox("Pleaase select one or more emails then try again", vbOKOnly)
Exit Sub
Else
For Each ItemCrnt In Exp.Selection
Call Type1Email(ItemCrnt)
Next
End If
End Sub
To use this macro, you select one or more Type1 emails and then run macro TestType1Email. This macro will pass the selected emails, one at a time, to the macro Type1Email. This will allow you to single step through macro Type1Email and ensure that it works to your entire satisfaction. I find this to be the easier method of testing a new Outlook macro.
It may be helpful to check what a rule can do for you. Select one of these emails and then click on Rules, which is in the middle of the Home tab, and then Create rule …. Selecting one of these emails means the first window is filled out with some options. Click Advanced options …. The new window lists all the options for selecting an email. Are all the options you need to select a type 1 or a type 2 email listed? The list is comprehensive but not complete. For example, you cannot select by the presence of attachments. Identify the options you can use and identify the options you need that are missing. Click Cancel twice to exist from rule creation.
You will need include code for any missing options in your macro.
Your question implies you have all the code you need for processing the emails except for suppressing the replace question. You need to check if there is an existing file before creating the new file. This is the routine that I use to check if a file exists:
Public Function FileExists(ByVal PathName As String, ByVal FileName As String) As Boolean
' Returns True if file exists. Assumes path already tested.
' Coded by Tony Dallimore
' Based on code written by iDevlop: http://stackoverflow.com/a/28237845/973283
' Ensure only one "\" between path and filename
If Right$(PathName, 1) <> "\" Then
PathName = PathName & "\"
End If
If Left$(FileName, 1) = "\" Then
FileName = Mid$(FileName, 2)
End If
FileExists = False
On Error Resume Next
FileExists = ((GetAttr(PathName & FileName) And vbDirectory) <> vbDirectory)
On Error GoTo 0
End Function
If the file exists, you can:
Use VBA statement Kill (https://learn.microsoft.com/en-gb/office/vba/Language/Reference/user-interface-help/kill-statement) to delete the old file.
Use VBA statement Name (https://learn.microsoft.com/en-gb/office/vba/language/reference/user-interface-help/name-statement) to move the old file to another folder or rename it perhaps by adding a date at the beginning of the name.
I favour the second option because I do not like deleting a file until I am really, really sure I will not need it again. I saw too many situations during my career where a file deleted as no longer needed was found to be incorrectly or incompletely processed a few months later.
Once you have fully tested the macros, you can create the rules to execute them. For each type of email:
Select an email of the required type.
Click on Rules and then Create rule ….
Tick any relevant boxes on the first window.
Click Advanced options ….
Tick all relevant boxes on the second window.
Click Next.
Tick the box against “Run a script”.
Click a script.
You will be shown a list of all the macros that can be run from a rule. Select the required macro.
Click Next.
Tick the box against any appropriate exceptions and enter any additional information required.
Click Next.
Name the rule. Tick “run this rule against any messages already in Inbox” if required. Review the rule and edit if necessary.
Click Finish.
I hope the above is enough to plug the holes in your knowledge.
I need to search through 9,000 emails and save the attachments with a certain timestamp (these are security camera feeds).
The code works on a small number of emails, but after about 20 the processing in Outlook appears to speed up significantly (attachments stop saving) and then Outlook hangs with a memory error.
My guess is the save step is not completed before the script moves to the next email in the target folder and therefore the backlog of saves becomes too large for Outlook.
' this function grabs the timestamp from the email body
' to use as the file rename on save in the following public sub
Private Function GetName(olItem As MailItem) As String
Const strFind As String = "Exact Submission Timestamp: "
Dim olInsp As Inspector
Dim wdDoc As Object
Dim oRng As Object
Dim strDate As String
With olItem
Set olInsp = .GetInspector
Set wdDoc = olInsp.WordEditor
Set oRng = wdDoc.Range
With oRng.Find
Do While .Execute(strFind)
oRng.Collapse 0
oRng.End = oRng.End + 23
strDate = oRng.Text
strDate = Replace(strDate, Chr(58), Chr(95))
GetName = strDate & ".jpg"
Exit Do
Loop
End With
End With
lbl_Exit:
Set olInsp = Nothing
Set wdDoc = Nothing
Set oRng = Nothing
Exit Function
End Function
Public Sub SaveAttachmentsToDisk24(MItem As outlook.MailItem)
Dim oAttachment As outlook.Attachment
Dim sSaveFolder As String
Dim strFname As String
sSaveFolder = "C:\Users\xxxxx\"
For Each oAttachment In MItem.Attachments
If oAttachment.FileName Like "*.jpg" Then
strFname = GetName(MItem)
oAttachment.SaveAsFile sSaveFolder & strFname
Set oAttachment = Nothing
Set MItem = Nothing
End If
Next oAttachment
There are other possibilities but my belief is that the memory errors are the result of creating Word objects and then not closing them. Om3r asked for more information but you ignored his requests making it impossible to provide a definitive answer. However, I wanted to prove it was possible to extract attachments from a large number of emails without problems so I have made some guesses.
I understand why you need a routine that will scan your Inbox for the backlog of 8,000 camera feed emails. I do not understand why you want to use an event to monitor your Inbox as well. I cannot believe this is a time critical task. Why not just run the scan once or twice a day? However, the routine I have coded could be adapted to create a macro to be called by an event routine. My current code relies of global variables which you will have to change to local variables. I am not a fan of global variables but I did not want to create a folder reference for every call of the inner routine and the parameter list for a macro that might be called by an event routine is fixed.
To test the code I planned to create, I first generated 790 emails to myself that matched (I hope) your camera feed emails. I had planned to create more but I think my ISP has classified me as a spammer, or perhaps a flamer, and it would not let me send any more. The body of these emails looked like:
xxx Preamble xxx ‹cr›‹lf›|
Exact Submission Timestamp: 2019-02-22 15:00:00 ‹cr›‹lf›|
xxx Postamble xxx ‹cr›‹lf›|
Your code requires the string “Exact Submission Timestamp:” followed by a date which you use as a file name. I have assumed that date in in a format that VBA can recognise as a date and I have assumed the date is ended by a standard Windows newline (carriage return, line feed). The second assumption would be easy to change. I have a routine that will accept many more date formats than VBA’s CDate which I can provide if necessary.
Each email has a different date and time between November, 2018 and February, 2019.
I would never save 8,000 files in a single disc folder. Even with a few hundred files in a folder, it becomes difficult to find the one you want. My root folder is “C:\DataArea\Test” but you can easily change that. Given the timestamp in my example email, my routine would check for folder “C:\DataArea\Test\2019” then “C:\DataArea\Test\2019\02” and finally “C:\DataArea\Test\2019\02\22”. If a folder did not exist, it would be created. The attachment is then saved in the inner folder. My code could easily be adapted to save files at the month level or the hour level depending on how many of these files you get per month, day or hour.
My routine checks every email in Inbox for the string “Exact Submission Timestamp:” followed by a date. If it finds those, it checks for an attachment with an extension of JPG. If the email passes all these tests, the attachment is saved in the appropriate disc folder and the email is moved from Outlook folder “Inbox” to “CameraFeeds1”. The reasons for moving the email are: (1) it clears the Inbox and (2) you can rerun the routine as often as you wish without finding an already processed email. I named the destination folder “CameraFeeds1” because you wrote that you wanted to do some more work on these emails. I thought you could move the emails to folder “CameraFeeds2” once you had completed this further work.
I assumed processing 790 or 8,000 emails would take a long time. In my test, the duration was not as bad as I expected; 790 emails took about one and a half minutes. However, I created a user form to show progress. I cannot include the form in my answer so you will have to create your own. Mine looks like:
The appearance is not important. What is important is the name of the form and the four controls on the form:
Form name: frmSaveCameraFeeds
TextBox name: txtCountCrnt
TextBox name: txtCountMax
CommandButton name: cmdStart
CommandButton name: cmdStop
If you run the macro StartSaveCameraFeeds it will load this form. Click [Start] to start the save process. You can let the macro run until it has checked every email in the Inbox or you can click [Stop] at any time. The stop button is not as important as I feared. I thought the routine might take hours but that was not the case.
You don’t report where your 8,000 emails are. I have an Inbox per account plus the default Inbox which I only use for testing. I moved the 790 test emails to the default Inbox and used GetDefaultFolder to reference it. I assume you know how to reference another folder if necessary. Note I use Session instead of a name space. These two methods are supposed to be equivalent but I always use Session because it is simpler and because I once had a failure with a name space that I could not diagnose. I reference folder “CameraFeeds1” relative to the Inbox.
You will have to adjust my code at least partially. For the minimum changes, do the following:
Create a new module and copy this code into it:
Option Explicit
Public Const Marker As String = "Exact Submission Timestamp: "
Public Const RootSave As String = "C:\DataArea\Test"
Public FldrIn As Outlook.Folder
Public FldrOut As Outlook.Folder
Sub StartSaveCameraFeeds()
' Reference outlook folders then pass control to frmSaveCameraFeeds
Set FldrIn = Session.GetDefaultFolder(olFolderInbox)
Set FldrOut = FldrIn.Parent.Folders("CameraFeeds1")
Load frmSaveCameraFeeds
With frmSaveCameraFeeds
.Caption = "Saving jpg files from Camera feed emails"
.txtCountCrnt = 0
.txtCountMax = FldrIn.Items.Count
.Show vbModal
End With
' Form unloaded by cmdStop within form
Set FldrIn = Nothing
Set FldrOut = Nothing
End Sub
Public Sub SaveCameraFeed(ByRef ItemCrnt As MailItem)
' Checks a single mail item to be a "camera feed" email. If the mail item is
' a "camera feed" email, it saves the JPG file using the date within the
' email body as the file name. If the mail item is not a "camera feed"
' email, it does nothing.
' To be a camera feed mail item:
' * The text body must contain a string of the form: "xxxyyyy" & vbCr & vbLf
' where "xxx" matches the public constant Marker and "yyy" is recognised
' by VBA as a date
' * It must have an attachment with an extension of "JPG" or "jpg".
' If the mail item is a camera feed email:
' * In "yyy" any colons are replaced by understores.
' * The JPG attachment is saved with the name yyy & ".jpg"
Dim DateCrnt As Date
Dim DateStr As String
Dim DayCrnt As String
Dim InxA As Long
Dim MonthCrnt As String
Dim PathFileName As String
Dim PosEnd As Long
Dim PosStart As Long
Dim SomethingToSave As Boolean
Dim YearCrnt As String
SomethingToSave = False ' Assume no JPG to save until find otherwise
With ItemCrnt
PosStart = InStr(1, .Body, Marker)
If PosStart > 0 Then
PosStart = PosStart + Len(Marker)
PosEnd = InStr(PosStart, .Body, vbCr & vbLf)
DateStr = Mid$(.Body, PosStart, PosEnd - PosStart)
If IsDate(DateStr) Then
DateCrnt = DateStr
For InxA = 1 To .Attachments.Count
If LCase(Right$(.Attachments(InxA).Filename, 4)) = ".jpg" Then
SomethingToSave = True
Exit For
End If
Next
End If
End If
If SomethingToSave Then
DateStr = Replace(DateStr, ":", "_")
YearCrnt = Year(DateCrnt)
MonthCrnt = Month(DateCrnt)
DayCrnt = Day(DateCrnt)
Call CreateDiscFldrIfItDoesntExist(RootSave, YearCrnt, MonthCrnt, DayCrnt)
PathFileName = RootSave & "\" & YearCrnt & "\" & MonthCrnt & "\" & DayCrnt & _
"\" & Trim(DateStr) & ".jpg"
.Attachments(InxA).SaveAsFile PathFileName
.Move FldrOut
End If
End With
End Sub
Public Sub CreateDiscFldrIfItDoesntExist(ByVal Root As String, _
ParamArray SubFldrs() As Variant)
' If a specified disk folder (not an Outlook folder) does not exist, create it.
' Root A disk folder which must exist and for which the user
' must have write permission.
' SubFldrs A list of sub-folders required within folder Root.
' Example call: CreateDiscFldrsIfNecessary("C:\DataArea", "Aaa", "Bbb", "Ccc")
' Result: Folder "C:\DataArea\Aaa\Bbb\Ccc" will be created if it does not already exist.
' Note: MkDir("C:\DataArea\Aaa\Bbb\Ccc") fails unless folder "C:\DataArea\Aaa\Bbb" exists.
Dim Filename As String
Dim Fldrname As String
Dim InxSF As Long
Fldrname = Root
For InxSF = LBound(SubFldrs) To UBound(SubFldrs)
Fldrname = Fldrname & "\" & SubFldrs(InxSF)
If Not PathExists(Fldrname) Then
Call MkDir(Fldrname)
End If
Next
End Sub
Public Function PathExists(ByVal Pathname As String) As Boolean
' Returns True if path exists
' Coded by Tony Dallimore
' Based on code written by iDevlop: http://stackoverflow.com/a/28237845/973283
On Error Resume Next
PathExists = ((GetAttr(Pathname) And vbDirectory) = vbDirectory)
On Error GoTo 0
End Function
I must warn you that I have modules full of standard routines that I use all the time. I believe I have included all the standard routines used by the code I have written for you. If the code fails because a sub or function is missing, post a comment and I will apologise and add the missing macro to my code.
Near the top of the above code is Public Const RootSave As String = "C:\DataArea\Test". You will have to change this to reference your root folder.
The first statement of Sub StartSaveCameraFeeds() is Set FldrIn = Session.GetDefaultFolder(olFolderInbox). Amend this as necessary if the emails are not in the default Inbox.
In the body of Sub StartSaveCameraFeeds() you will find PosEnd = InStr(PosStart, .Body, vbCr & vbLf). If the date string is not ended by a standard Windows’ newline, amend this statement as necessary.
Create a user form. Add two TextBoxes and two CommandButtons. Name them as defined above. Copy the code below to the code area of the form:
Option Explicit
Private Sub cmdStart_Click()
' Call SaveCameraFeed for every MailItem in FldrIn
Dim CountMax As Long
Dim InxI As Long
Dim MailItemCrnt As MailItem
With FldrIn
CountMax = FldrIn.Items.Count
For InxI = CountMax To 1 Step -1
If .Items(InxI).Class = olMail Then
Set MailItemCrnt = .Items(InxI)
Call SaveCameraFeed(MailItemCrnt)
Set MailItemCrnt = Nothing
End If
txtCountCrnt = CountMax - InxI + 1
DoEvents
Next
End With
Unload Me
End Sub
Private Sub cmdStop_Click()
Unload Me
End Sub
The form code should not need amendment.
As I have already written, this code processed 790 camera feed emails in about one and a half minutes. I coded a further routine that checked that for every email the date matched the name of a jpg file. I could include this routine in my answer if you would like to perform the same check.
My client service system sends email notifications when a new inquiry comes in. I am able to reply to the notification and the system will update the inquiry with information from my email reply.
Reply example:
To: "client inquiry system"
Subject: Re: I am having password trouble Inquiry:5601
Body of email below:
Your password has been reset.
The above will append "Your password has been reset." to the inquiries description.
I am also able to trigger changes to Status ( i.e. Closed, Resolved, Defunct) if I place special syntax at the top of the email body.
To: "client inquiry system"
Subject: Re: Inquiry:5601 -- I am having password trouble
Body of email below:
Status=Closed
Your password has been reset.
The above will set the inquiry to Closed in my system.
I would like to use a form or macro button that will provide users with drop down selections or free form text that will be added to the top of the email body once set.
I have some familiarity with VBA, but very new. Please help!
I am not convinced by your reply to my comment but this answer is an attempt to be helpful. It includes four macros that demonstrate functionality you will need. I hope it is enough to get you started.
When you open Outlook’s Visual Basic Editor, you will see something like the following down the left side of the screen. If you do not see it, click Ctrl+R.
- Project 1 (VbaProject.OTM)
- Microsoft Office Outlook Objects
ThisOutlookSession
- Modules
Module1
The hyphens will be in little boxes. If any hyphen is a plus, click the plus to expand the list under the heading.
Click ThisOutlookSession. You will get an empty code area on the right. This is like a module code area but is used for event routines. Copy this code into that area:
Option Explicit
Public WithEvents MyNewItems As Outlook.Items
Private Sub Application_Startup()
' This event routine is called when Outlook is started
Dim NS As NameSpace
Dim UserName As String
Set NS = CreateObject("Outlook.Application").GetNamespace("MAPI")
With NS
UserName = .CurrentUser
Set MyNewItems = .GetDefaultFolder(olFolderInbox).Items
End With
MsgBox "Welcome " & UserName
End Sub
Private Sub myNewItems_ItemAdd(ByVal Item As Object)
' This event routine is called each time an item is added to Inbox
' because of:
' Public WithEvents MyNewItems As Outlook.Items
' Set MyNewItems = .GetDefaultFolder(olFolderInbox).Items
With Item
Debug.Print "#####" & Format(Now(), "dMmmyy hh:mm:ss") & _
": Item added to Inbox with Subject: [" & .Subject & _
"] from [" & .SenderEmailAddress & "] with Text body"
Debug.Print .Body
End With
End Sub
Close Outlook and click Yes for “Do you want to save the VBA project ‘VbaProject.OTM?’”
Reopen Outlook. You will be told a program is trying to access email addresses. Click Allow access for, select 10 minutes and click Yes. You will get a window saying “Welcome John Doe”.
If this does not happen, select Tools then Macros then Security. Security level Medium must be selected to use macros safely.
The macro Application_Startup() has accessed Outlook’s email database. It is not easy to avoid the user being asked to allow access since Outlook has a very robust security system. There is a four step self-certification process which should allow you suppress this question for your own macros. I have successfully performed the first three steps but have never mastered the fourth step. I have carefully followed such instructions as I can find on the web but nothing has worked for me. Perhaps you will be more successful or perhaps you have access to an expert who can guide you if you want to suppress this question
The macro Application_Startup() has done two things: issued the welcome message and initialised MyNewItems. The welcome message is just a demonstration that you can access the user’s name which might be useful if you have a shared Inbox. Initialising MyNewItems activates the event routine myNewItems_ItemAdd(). This outputs details of the each new item to the Immediate Window.
This is a quick demonstration of event routines which I thought would be useful to you. However, I have discovered that if myNewItems_ItemAdd() is busy with one item when a second arrives, it is not called for the second item. I use a very old version of Outlook and this may be a bug that has been cleared in later releases. If you decide to use event routines, you need to check this out.
Another way of getting access to emails is Explorer. Insert a new module and copy the following code into it:
Option Explicit
Public Sub DemoExplorer()
Dim Exp As Outlook.Explorer
Dim ItemCrnt As MailItem
Dim NumSelected As Long
Set Exp = Outlook.Application.ActiveExplorer
NumSelected = Exp.Selection.Count
If NumSelected = 0 Then
Debug.Print "No emails selected"
Else
For Each ItemCrnt In Exp.Selection
With ItemCrnt
Debug.Print "From " & .SenderName & " Subject " & .Subject
End With
Next
End If
End Sub
DemoExplorer() shows another way of giving a macro access to mail items. The user selects one or more emails and then activates the macro DemoExplorer(). Again this just outputs some properties of a mail item to the Immediate Window.
Click F2 and the code window is replaced by a list of libraries. Scroll down the list of Classes and select MailItem. The right hand window displays all the members of MailItem. Some, such as ReceivedTime, are obvious but you will probably have to look up most. I suggest you make a note of all that look useful. Click a module, to get back to a code window when you have finished.
DemoReply(), below, is an updated version of DemoExplorer() which replies to selected emails. Add this code to your module:
Public Sub DemoReply()
Dim Exp As Outlook.Explorer
Dim ItemCrnt As MailItem
Dim Reply As MailItem
Dim Subject As String
Dim SenderAddr As String
Dim Received As Date
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Debug.Print "No emails selected"
Else
For Each ItemCrnt In Exp.Selection
' Get properties of message received
With ItemCrnt
Subject = .Subject
SenderAddr = .SenderEmailAddress
Received = .ReceivedTime
End With
' Create reply
Set Reply = CreateItem(olMailItem)
With Reply
.BodyFormat = olFormatPlain
.Body = "Thank you for your enquiry" & vbLf & _
" Subject: " & Subject & vbLf & _
" Received at: " & Format(Received, "d Mmm yyyy h:mm:ss") & vbLf & _
"which will be handled as soon as an analyst is available."
.Subject = "Thank you for your enquiry"
.Recipients.Add SenderAddr
' Display allows the user to review the reply before it is written to Outbox
' but control is not returned to this macro. Only the first select mail item
' will be processed
' Send gives the user no opportunity to review the replies but the macro does not
' use control so all replies are sent.
'.Display
.Send
End With
Next
End If
End Sub
I use an Outlook address for my private email and a Gmail address for my public email. I sent myself some text emails from the Gmail address. In Outlook, I selected these emails and activated DemoReply(). The expected replies arrived in my Gmail Inbox. Try sending yourself some emails and the try replying.
To demonstrate the use of a useform within Outlook, I inserted a new form and left the name as the default UserForm1. I dragged two text boxes to the form which I left with their default names of TextBox1 and TextBox2. I also dragged a command button which I renamed cmdSend.
An Outlook macro can only communicate with a user form via global variables. Add the following at the top of the module; they must be placed before any macros:
Public Box1 As String
Public Box2 As String
Add this macro to the module:
Sub DemoForm()
' Initialise global variables to be used by form before it is loaded
Box1 = "Initial value for text box1"
Box2 = "Initial value for text box2"
Load UserForm1
UserForm1.Show vbModal
' Control does not return to this module until user releases control of form
Debug.Print Box1
Debug.Print Box2
End Sub
Add this code to the form:
Private Sub cmdSend_Click()
Box1 = TextBox1
Box2 = TextBox2
Unload Me
End Sub
Private Sub UserForm_Initialize()
TextBox1 = Box1
TextBox2 = Box2
End Sub
Activate DemoForm(). The form will appear with the text boxes set to "Initial value for text box1" and "Initial value for text box2". Change these values and click Send. Control will be returned to DemoForm() which outputs the new values to the Immediate Window.
Can anyone help me figure out what's going wrong and how to fix it?
I'm trying to automate sending an email with some daily status information. I'd tried automating this from Access but kept running into (known but apparently unsolved) problems with GetObject(, "Outlook.Application") with Windows 8.1 64 and Outlook 2013. So I decided to automate starting from Outlook.
Anyway, I moved the mail message creation code into Outlook vba and had it start Access and run the Access code. This is all well and good until I get to creating the mail message. Everything starts just fine until it gets to writing to the body of message (using Word as the body editor). At the first "TypeText" command, I'm getting the error message in the title. If I click debug on the error notification dialog and then single-step through the line of code in question, it works just fine. I thought that there was some timing problem, so I stuck a 2-second wait in the code. No luck. The code in question, with some other oddities associated with testing (notably trying to type and then delete text), is below:
Public Sub CreateMetrics()
' Mail-sending variables
Dim mailApp As Outlook.Application
Dim accessApp As Access.Application
Dim mail As MailItem
Dim wEditor As Word.Document
Dim boolCreatedApp As Boolean
Dim i As Integer
Set mailApp = Application
' Create an Access application object and open the database
Set accessApp = CreateObject("Access.Application")
accessApp.OpenCurrentDatabase dbLoc
accessApp.Visible = True
' Open the desired form and run the click event hander for the start button
accessApp.DoCmd.OpenForm ("ProcessStatus")
accessApp.Forms![ProcessStatus].StartButton_Click
' Create the outgoing mail message
Set mail = Application.CreateItem(olMailItem)
mail.Display
mail.BodyFormat = olFormatHTML
Set wEditor = mailApp.ActiveInspector.WordEditor
With accessApp.Forms![ProcessStatus]
Debug.Print .lblToList.Caption
Debug.Print .lblSubject.Caption
Debug.Print .lblIntroduction.Caption
Debug.Print .lblAttachFilepath.Caption
End With
mail.To = accessApp.Forms![ProcessStatus].lblToList.Caption
mail.Recipients.ResolveAll
mail.Subject = accessApp.Forms![ProcessStatus].lblSubject.Caption
mail.Attachments.Add accessApp.Forms![ProcessStatus].lblAttachFilepath.Caption
Sleep 2000
' Error occurs in the next line ***********************************************
wEditor.Application.Selection.TypeText Text:="Test"
wEditor.Application.Selection.HomeKey
wEditor.Application.Selection.Delete Count:=4
wEditor.Application.Selection.PasteSpecial DataType:=wdPasteBitmap
wEditor.Application.Selection.HomeKey
wEditor.Application.Selection.TypeText accessApp.Forms![ProcessStatus].lblIntroduction.Caption
wEditor.Application.Selection.TypeText Text:=Chr(13) & Chr(13)
wEditor.Application.Selection.EndKey
' wEditor.Application.Selection.EndKey
' wEditor.Application.Selection.TypeText Text:=Chr(13)
' wEditor.Application.Selection.TypeText Text:=configs("EmailSignature")
' End With
With mailApp.Session.Accounts
i = 1
Do While i <= .Count
' Use either the specified email address OR the last outlook email address
If RegEx_IsStringMatching(.Item(i).SmtpAddress, accessApp.Forms![ProcessStatus].lblSenderRegex.Caption) Or i = .Count Then
mail.SendUsingAccount = .Item(i)
i = .Count + 1
Else
i = i + 1
End If
Loop
End With
mail.Save
accessApp.Quit
End Sub
I added a "mail.Display" just before the line that was causing the failure, which seemed, incorrectly, to have fixed the problem.
I have now solved this problem by executing a document.select on the document associated with the email I was creating. To select the right document (there doesn't seem to be any guarantee of which one that would be within the wEditor.Application.Documents collection, though it was typically the first one), I created an almost-certainly unique piece of text and assigned it to the body of the email, which I could then go and find. Here's the new code that I added to the code above:
Dim aDoc As Word.Document
Dim strUniqueID As String
. . .
mail.Attachments.Add accessApp.Forms![ProcessStatus].lblAttachFilepath.Caption
strUniqueID = accessApp.Forms![ProcessStatus].lblSubject.Caption & Rnd(Now()) & Now()
mail.Body = strUniqueID
' Search for the unique text. aDoc.Content has extra characters at the
' end, so compare only for the length of the unique text
For Each aDoc In wEditor.Application.Documents
If Left(aDoc.Content, Len(strUniqueID)) = strUniqueID Then
aDoc.Select
mail.Body = ""
End If
Next aDoc
wEditor.Application.Selection.TypeText Text:="Test"
. . .
I looked at a lot of examples of code that did this kind of thing. None of them performed a select or said anything about needing one. Debugging was made that much harder because the select occured implicitly when the debugger was invoked.
I receive an email report twice every workday. Sometimes the machine that sends these reports crashes and no emails are sent out. What I am wanting to do is use some Outlook VBA to check if an email hasnt arrived at 12:15 and 17:05.
Finding an email that is there is fairly easy, but finding one that isnt is making me scratch my head a bit. I have a class module set up right now (I assume that would be the way to go) and have the code for what I want to do if no email has been received, but cannot figure out how to go about checking for the email at those times. It might be something simple, but have not really scripted in Outlook VBA before, so am not sure where to start.
The method pointed out in a comment.
Outlook VBA - Run a code every half an hour
Outlook VBA - Run a code every half an hour with outlook 2010 64 bits
A possibly simpler alternative. Set a recurring task with a reminder.
In ThisOutlookSession
Private Sub Application_Reminder(ByVal Item As Object)
If Item.Class = olTask Then
If InStr(Item.Subject, "subject") > 0 Then
ReminderUnreceivedMail
End If
End If
End Sub
Sub ReminderUnreceivedMail()
Dim Itms As Items
Dim srchSender As String
Dim srchSubject As String
Set Itms = GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Items
srchSender = "sender"
srchSubject = "subject"
Set Itms = Itms.Restrict("[SenderName] = 'sender' And [Subject] = 'subject' And [SentOn] > '" & Format(Date, "yyyy-mm-dd") & "'")
If Itms.count = 0 Then
MsgBox "No " & srchSubject & " email on " & Format(Date, "yyyy-mm-dd")
End If
Set Itms = Nothing
End Sub