0

Company i work for manually saves certain requests (sent per mail) to a shared drive, renaming them as such: "YYYYMMDD_Firstname_Lastname". The mails are saved as .msg

Since we get about a hundred of these per week, I'd like to macro this so I don't waste time.

The article here: Outlook VBA macro for saving emails copies in a local folder explains how to save files locally, but I'd like to make following additions: - Rename the copy before it gets saved to the shared drive (manually if needed) - Select the shared path it needs to be saved to (preferably a drop-down with three choices) - create a proper userform for this

If anyone could assist with the code, or provide me with tutorials/guides on how to do this myself, I'd be extremely grateful.

P.S. just started using and creating macro's a week ago. Still very much a beginner. any link to a good tutorial for developers would be greatly appreciated, regardless of whether it answers my questions.

Thanks guys!

Used the code described in the article as such:

Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
    Call SaveACopy(Item)
End Sub

Sub SaveACopy(Item As Object)
    Const olMsg As Long = 3

    Dim m As MailItem
    Dim savePath As String

    If TypeName(Item) <> "MailItem" Then Exit Sub

    Set m = Item

    savePath = "c:\users\your_user_name\desktop\"  '## Modify as needed
    savePath = savePath & m.Subject & Format(Now(), "yyyy-mm-dd-hhNNss")
    savePath = savePath & ".msg"


    m.SaveAs savePath, olMsg


End Sub

Update: using the macro provided by Tony Dallimore I've amanaged to identify that .SenderName is the main info I need from the mails to be processed. All I need now is to replace the spaces in that output by underscores, and add the date in reverse in front of it to have my filename.

Thanks a bunch to Tony Dallimore for the continuous assistance on this project.

Since it seems somewhat confusing looking back on my original question, I'll try to clarify:

I get about 100 mails a week informing us of approvals of certain user requests. Company policy is to save these mails as .msg on a shared drive used for administration before processing the request. The filename of these messages needs to be as such: "YYYYMMDD_FIRSTNAME_LASTNAME.msg" (with YYYY being the year, MM being the month, and DD being the day on which we received these mails)

We get three main "types" of such mails, saved in different locations, but using the same filename respectively.

What I'd need is a macro or set of macros that can save these mails in the correct networkdrive under the correct format at the press of a button, or using minimal clicks/manual input.

I've decided to use .SenderName and .Senton, since those seem to give me most of what I need.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Tman
  • 37
  • 10
  • `savePath` is the full Path and File name of the saved message. Currently the name is `.Subject` followed by the time saved. I normally use `.ReceivedTime` instead of `Now().` You can change the path and or file name to something else. What name do you want? – Tony Dallimore Apr 08 '19 at 12:41
  • I cannot find an online or book tutorial I like. There are lots of high quality routines available but sometimes you need some background to understand them. When I have sometime, I plan to have a go myself. Does this answer of mine help: https://stackoverflow.com/a/8699250/973283. – Tony Dallimore Apr 08 '19 at 12:47
  • When you type your question there are some options above the edit window. One option is { }. Use this option to format your code. I have done it for you. Previously, the first and last line of your code were not part of the code block – Tony Dallimore Apr 08 '19 at 13:10
  • Outlook supports a number of events. You are using `ItemSend` but your question says you "get" these emails. Should you be using event `ItemAdd` which handles new entries in your Inbox? I prefer using a rule with a script over event `ItemAdd`. – Tony Dallimore Apr 08 '19 at 13:14
  • Thanks for the replies! I'll use {} in the future, thanks for the tip. – Tman Apr 08 '19 at 15:20
  • What I'd need specifically, is to make a copy of the mail received and store that copy on a shared drive. The format of storage would be the date, but in reverse (YYYYMMDD), followed by an underscore, and the first and last name of the user, seperated by underscores. This should probably be a manual entry, since the mails are auto-generated by the system, and include a screenshot of the details of the user in question. Would ItemAdd provide the same fucntionality? Again, thanks for the replies. – Tman Apr 08 '19 at 15:24
  • My answer should identify if first and last name are available from the emails properties. Which date: date sent, date received, date saved, date within email body or some other date? Does the save folder **have** to be a user choice? I would not be happy if at random times during the day I was interrupted to decide which folder was appropriate for an email. – Tony Dallimore Apr 08 '19 at 20:41
  • I give a brief introduction to events and event macros in my answer. Sending an email triggers an ItemSend Event. Receiving an email or moving it to another folder triggers an ItemAdd event. The code for different events is similar; it is just a case of identifying the appropriate trigger for your needs. – Tony Dallimore Apr 08 '19 at 20:55
  • Please note that no one is told that you have edited your question. It is an "old" question so I doubt people are rereading it. You get a message if an answer or a comment is posted against your question. An answerer gets a message if a comment is posted against their answer. Including Xyyyy where X is @ and yyyy is a username in a comment ensures user yyyy gets a message. I check answers and comments for a day or two after posting them which is how I noticed your edit. – Tony Dallimore Apr 09 '19 at 16:19
  • You seek a file name with a format of "YYYYMMDD_John_Doe.msg". You say you get about 100 of these messages per week which means about 20 messages will be saved with the same file name. If these are generated by the system, there is a good possibility that two might be generated within the same second so adding time may not be enough to ensure names are unique. You need to include a sequence number in the file name. – Tony Dallimore Apr 09 '19 at 16:52
  • I understand the need to have a single archive of all these messages. You say "I get about 100 emails a week informing us ...". Do you mean there are a total of 100 messages per week or you and a number of colleagues each get 100 messages per week? Even if it is only 100 messages in total that is still over 5,000 per year spread over three folders. Should you need to refer to one of these messages, finding the correct one will be a laborious process. I would not contemplate an archive like this without an index nor without subfolders by month. – Tony Dallimore Apr 09 '19 at 16:56
  • How do you identify these messages? Do they have a particular subject or a particular string within the body? How do you determine which folder is appropriate for a given message? – Tony Dallimore Apr 09 '19 at 16:56
  • Yes, there are quite a few mails like this coming in weekly (100 total). We're responsible for these requests for the entire EMEA area, so I'm not surprised to see 5000 per year tbh. Yes, the requests are subdivided per year - new folder created every year. Since the date in reverse per user per type of request is usually unique (nobody will ask for the same thing twice a day), we have no issues finding the mails if required. These mails are generated by the system, which gives them a unique subject, starting with [approved], followed by the request type. – Tman Apr 10 '19 at 06:17
  • Just to mention as well: there is no need to autodetect these mails in our inboxes. We can still do that manually - we know what kind of request goes where. I'm just looking to select the mail, press the macro button in the ribbon, and not worry about it anymore (correct save location/name). – Tman Apr 10 '19 at 06:21
  • The system does send the approvals for the user request trough name of the user itself (send as user), so the .Sendername will be different per user. Add to that the ReceivedOn date, which we're using now, and the filename will certainly be unique. There is a program used internally to find these mails, which is why the filename has to follow the format previously mentioned. – Tman Apr 10 '19 at 06:34
  • Even so, I might add a userform to show the filename before saving it to the folder, with the option to alter it if incorrect, just to be sure it's fine. For now, this is just something I'd like to test the possibilities of, not something I will actually start using in production without testing and manager's approval. – Tman Apr 10 '19 at 06:36
  • In your question you have three statements that build `savePath`. The first builds the folder name. The second builds the file name except for the extension. The third adds the extension. You need to replace the first two statements to create the folder and file names you need. – Tony Dallimore Apr 10 '19 at 10:06
  • If I understand correctly, the subject contains the information identifying the request type which identifies the destination folder name. You have a shared folder with a name such as "P:\EMEA Requests". Within that you have subfolders: "2019 xxxx", "2019 yyyy" and"2019 yyyy" where "xxxx", "yyyy" and "zzzz" identify the three request types. You want messages saved in the appropriate subfolder according to the request type in the subject. Is that correct? What is the format of these subjects? It should be reasonably easy to extract the request type from the subject and generate the path name. – Tony Dallimore Apr 10 '19 at 10:07
  • I understand `SenderName` is something like "John Doe". You want that changed to "John_Doe". The function `Replace` will do that. – Tony Dallimore Apr 10 '19 at 10:07
  • Thanks again for the assistance! I can really see this taking shape now. You're correct in your reasoning for the request types. unfortunately, there is no real set value that will appear in the subject, other than [approved]. What folder it needs to go to is per-case. Unfortunately, the company doesn't use preset templates to formulate these requests, so the subject will be whatever the original requester registered in the system. – Tman Apr 10 '19 at 10:59
  • Because of that, I was looking to create three seperate buttons, or three options in the macro. That way, we can just select the request type (Hardware, software network - or somthing along those lines), and have the mail saved in the correct folder based on that input. This will still be a lot faster than having to save the mail in the folder and renaming it, is what I hope. It also prevents people from accidentally opening the wrong path and putting these where they don't belong. – Tman Apr 10 '19 at 11:02
  • I have added a second suggestion for generating PathName to my second answer that can handle multiple keywords within a subject. – Tony Dallimore Apr 10 '19 at 12:04

2 Answers2

0

This is not a direct answer to your question. It is an investigation which I hope will provide the information necessary for an answer.

You say “… mails are auto-generated by the system …”. This may explain why I do not fully understand why your code works. I will explain my confusion after I have provided some background.

There are four distinct methods by which a MailItem can be selected for processing:

  1. The user can select one or more emails and then call a macro to process the selected MailItem. (Note it is an email to the user but a MailItem to a macro.)
  2. A macro can read up or down a folder of MailItems, reviewing properties to determine which are to be processed. Sort and Filter can be used to more quickly target the MailItems of interest.
  3. You can specify a rule that will look at each email as it arrives and review properties such as subject and sender. If the email has the required properties, a number of actions can be performed. If the standard actions are not adequate, you can link a macro to perform any action available to a VBA macro.
  4. You can instruct Outlook to call a macro whenever a particular event occurs. Events include: MailItem added to folder Xxxx,MailItem opened, MailItemsent, MailItemsaved, MailItem closed, MailItem replied to or MailItem forwarded.

Your code is using approach 4. In particular, you are using a MailItemsent event. You say “ … we get about a hundred of these [emails] per week …”. If “get” is the correct word, I would expect MailItem added to folder Inbox to be the appropriate event. Perhaps your code works because the system is generating emails from user X to user X.

If these emails are generated by the system, we cannot be sure what properties are set and what values they are set to. Please copy the code below to an Outlook module. Select one or more of these emails and run macro CallSubForSelectedEmails.

Option Explicit
Public Sub CallSubForSelectedEmails()

  Dim Exp As Explorer
  Dim ItemCrnt As MailItem

  Set Exp = Outlook.Application.ActiveExplorer

  If Exp.Selection.Count = 0 Then
    Call MsgBox("Please select one or more emails then try again", vbOKOnly)
    Exit Sub
  Else
    For Each ItemCrnt In Exp.Selection
      If ItemCrnt.Class = olMail Then
        Call DsplSimpleProperties(ItemCrnt)
      End If
    Next
  End If

End Sub
Sub DsplSimpleProperties(ItemCrnt As Outlook.MailItem)

  Dim InxR As Long

  Debug.Print "=============================================="
  Debug.Print "  Profile: " & Session.CurrentProfileName
  Debug.Print "     User: " & Session.CurrentUser
  With ItemCrnt
    Debug.Print "  Created: " & .CreationTime
    Debug.Print " Receiver: " & .ReceivedByName
    Debug.Print " Received: " & .ReceivedTime
    For InxR = 1 To .Recipients.Count
      Debug.Print "Recipient: " & .Recipients(InxR)
    Next
    Debug.Print "   Sender: " & .Sender
    Debug.Print " SenderEA: " & .SenderEmailAddress
    Debug.Print " SenderNm: " & .SenderName
    Debug.Print "   SentOn: " & .SentOn
    Debug.Print "  Subject: " & .Subject
    Debug.Print "       To: " & .To
  End With

End Sub

For one of my emails, this routine outputs:

==============================================
  Profile: Outlook
     User: Tony Dallimore
  Created: 08/04/2019 19:59:22
 Receiver: Tony Dallimore
 Received: 08/04/2019 18:45:39
Recipient: a.j.dallimore@acmeisp.com
   Sender: Lifecake
 SenderEA: support@lifecake.com
 SenderNm: Lifecake
   SentOn: 08/04/2019 18:45:37
  Subject: ?? Someone commented on Alex and Eric's video
       To: a.j.dallimore@acmeisp.com

Note 1, I am both the system user and the receiver of this email. This gives two possible ways of getting my first and last names. I use initials in my email address but your company may use names.

Note 2: my code uses approach 1 to select the emails to be processed. Macro CallSubForSelectedEmails calls macro DsplSimpleProperties for each selected email. I do all my investigations and all my development of email processing macros using code like this. This gives me complete control over which emails are processed. The call profile for macro DsplSimpleProperties is the same as that for a rule macro or an event macro. Once I have debugged my macro using approach 1 and switch to calling it from a rule or an event with minimal additional testing. I know of no easier way of debugging email processing macros.

Tony Dallimore
  • 12,335
  • 7
  • 32
  • 61
  • Thanks for the assistance. I do get similar output to the one you display here. – Tman Apr 09 '19 at 07:31
  • I'll investigate further on how we can identify these mails, and will post a new question as soon as i have a clearer understanding on them, as well as a concept on how to create the actual macro itself. Thanks a bunch for the info, though! – Tman Apr 09 '19 at 07:34
  • Slight update: Using your macro to investigate the mails, I've discovered that the system sends the mails Whilst identifying as the user itself. That is a huge step forward, since that makes .SenderName a good parameter to use going forward. – Tman Apr 09 '19 at 07:50
0

Again this is not a complete answer because I do not have the information for a complete answer.

Task 1: Generate PathName

The information for the path name comes from the MailItem's Subject. For this example, I assume the request type is 1, 2 or 3 and it is the last character of the subject.

Dim PathName As String

' Generate end of subfolder name
Select Case Right$(ItemCrnt.Subject,1)
  Case "1"
    PathName = "xxxx"
  Case "2"
    PathName = "yyyy"
  Case "3"
    PathName = "zzzz"
  Case Else
    ' Subject does not conform to expected format.
    Exit Sub
End Select  

' Prefix root folder name and year of subfolder name
PathName = "P:\EMEA Requests\" & Year(ItemCrnt.SentOn) & "\" & PathName

Right$ is a function that extracts a specified number of trailing characters from a string. Functions Left$ and Mid$ are also available. If the subject is sufficiently complicated, we can consider Regex. Year is a function that extracts the year from a date. The value will be an integer but VBA will automatically convert it to a string if it used as a string.

If the routine cannot identify the request type, it abandons the MailItem. I will discuss this issue later.

Task 1; Suggestion 2: Generate PathName

You say the subjects lack a fixed format and just include words from the original request. You imply these words are good enough for a human to identify the request type. So the words for a request might include "hardware", "h'ware", "computer" or "laptop". Another request might include "software", "application or "app". This is a simple method of handling this type of situation. There is a better method which I will introduce if this looks feasible.

If Instr(1, LCase(ItemCrnt.Subject), "hardware") <> 0 Then 
  PathName = "xxxx"
ElseIf Instr(1, LCase(ItemCrnt.Subject), "h'ware") <> 0 Then 
  PathName = "xxxx"
ElseIf Instr(1, LCase(ItemCrnt.Subject), "computer") <> 0 Then 
  PathName = "xxxx"
ElseIf Instr(1, LCase(ItemCrnt.Subject), "laptop") <> 0 Then 
  PathName = "xxxx"
ElseIf Instr(1, LCase(ItemCrnt.Subject), "software") <> 0 Then 
  PathName = "yyyy"
ElseIf Instr(1, LCase(ItemCrnt.Subject), "application") <> 0 Then 
  PathName = "yyyy"
ElseIf Instr(1, LCase(ItemCrnt.Subject), "app") <> 0 Then 
  PathName = "yyyy"
Else
  PathName = ""
End If

You can keep adding possible keywords until your requestors run out of alternatives. Failing that you can use your userform with buttons approach after the macro has handled the easy messages.

Task 2: Generate FileName

Dim FileName As String

FileName = Format(ItemCrnt.SentOn, "yymmdd") & " " & Replace(ItemCrnt.SenderName," ", "_")

Task 0: Design

Before coding can start, you need to design the total process. You can start with something simple and then develop it as you better understand your requirement. You can code little bits as I did with PathName and FileName so you can understand the bits you need to fit together. But tackling something complex without a plan rarely ends satisfactorily.

My understanding of your requirement is incomplete but I will have a go at a design.

I would have a Rule that copied incoming emails of this type to an Outlook folder such as "Unsaved EMEA Requests". Note: these are copies; the original remains in the Inbox for processing as required. I assume there is a way to identify these emails that is within the functionality available to a rule.

I would have all the code in a macro which I would call once or twice a day as appropriate. This macro would read up folder "Unsaved EMEA Requests". If it can generate a path and file name for a message, it will save the message to the required disc folder and delete the message from the Outlook folder. If it could not process a message, it would leave it in Outlook folder "Unsaved EMEA Requests". If a message is left in Outlook folder "Unsaved EMEA Requests", you will know (1) that the macro needs enhancing to handle a previously unencountered message type or (2) the rule needs amending because it has copied the wrong sort of message.

I said "read up folder" not "read down folder". You access a MailItem within a folder by its position: 1, 2, 3, … Folder.Count. If you delete MailItem 2 then MailItem 3 becomes MailItem 2, MailItem 4 becomes MailItem 3 and so on. The value of Folder.Count is reduced by one. You sometimes see questions asking why their macro is only processing every other MailItem. The reason is they have coding like:

For InxI = 1 to Folder.Count
  ' Process and delete Folder.Item(InxI)
Next

With the above code, you process items 1, 2, 3 in turn. If you delete item 2, you will skip the original item 3 because it is now item 2.

The correct code is:

For InxI = Folder.Count To 1 Step -1
  ' Process and delete Folder.Item(InxI)
Next

With this code you process items 10, 9, 8, 7 in turn. If you delete item 9, you do not care that item 10 has become item 9 because you are now processing item 8.

If you are only reading items, you do not need to worry above this issue. But if you are adding or deleted items, you do need to worry about it.

Tony Dallimore
  • 12,335
  • 7
  • 32
  • 61