Emails are used a lot in project communication, so users want to be able to archive emails to ProjectWise so that they have all project information in a single location. This article demonstrates how task of archiving project emails to ProjectWise could be made easier by automation.
We will create a flow that will periodically scan users Outlook 365 inbox for new project emails. It will store emails into folders of corresponding work area in ProjectWise. All users needing their emails to be archived would have to run a copy of such flow.
This flow will prevent archiving of duplicate emails. Very often same email is sent to multiple recipients in organization. An email will not be archived if another recipient has already archived this message.
We had to make several assumptions about how users organize emails and project data. As most of ProjectWise configurations are unique, you may need to make adaptations to make it practical in your environment. Here are the assumptions:
This Flow was created using ProjectWise sample datasource available with ProjectWise Design Integration server installation. Every ProjectWise configuration is unique, so this Flow may need to be adapted to your actual configuration and project requirements.
This flow handles data in several projects so we have created a new shared work area and associated cloud project called ‘Admin’. We used this project for all Flow actions to make them independent of a specific project where engineering work is performed.
The flow consists of three major parts: Get list of projects, Find project email, Save emails to ProjectWise. We have used scopes (the brown action) to group actions for better readability.
The flow runs automatically every hour.
At the end of this part there will be an array of project folder names (later used for building path to project folder) and project number (later used for email folder name in Outlook 365).
The first action in this section is ProjectWise ‘Find folders by saved search’. This action returns list of project folders (work areas from sample PW datasource in our case).
Next is a build-in Data Operations > Select action to transform list of folders from search to list of folder names and document numbers. Folder name is taken directly from search results. Project Number in this example is extracted from project name. Project name is in format ‘BSI000 – Project name’. The part before dash is the number. The formula to extract number is (use Expression tab to enter it):
substring(item()?['name'], 0, max(indexOf(item()?['name'], ' - '), 0))
The third action is Data Operations > Filter Array. It filters only entries that have a document non-empty number. Notice that this action was renamed to ‘ProjectList’ for easier access in following actions. The output of this action is the list of projects that will be monitored for emails.
This part will look for emails for each project and save the list into array variable ‘EmailsWithProjects’. This variable is defined outside of scopes, because variables can only be declared at top level.
The following scope of ‘Find project emails’ is shown in the picture:
The flow of this part is:
Let’s look at some details of these steps.
Get Emails V2: Reading of email tries to get as much emails as possible. At this point we do not know how much is archived already, but it assumes that the flow runs periodically multiple times a day. The filters are set to return messages from today and yesterday This is a generous overlap, but it is best what Outlook connector filtering allows.
Outlook 365 get emails actions can get maximum 25 messages. If there were more than that received since last flow run, the messages above 25 will not be fetched. Unfortunately, it is a limitation of this flow that cannot be easily overcome with current implementation of Outlook 365 connector.
The connector also constructs email folder name using project number from ProjectList. The formula for folder path is like this: Projects/@{item()?['number']}
Projects/@{item()?['number']}
Get Emails action may fail if there are no folder with project name. To handle this situation without failing the flow we use the following run after settings (to access run after settings for an action go to: … > Configure run after):
This sequence ensures that in case of failure of Get Emails, a compose action will be executed. This action will always succeed so the flow will continue to run without error. When Get Emails does not fail if will to ‘Apply to each 2’ loop for each email.
The Apply to Each 2 loop appends full email information along with project name into array for processing in next section.
It is the last and most complicated section.
This scope uses two compose actions to extract message ID from email. It is an email header that is in format: Message-ID: <unique id>. We need the ‘unique id’ part of it.
Compose 2 finds the Message-ID header and takes 260 symbols that will contain the unique ID. The expression is (it is a single line):
substring(body('Export_email'), indexOf(body('Export_email'), concat(json('{\"NL\":\"\\n\"}')?['NL'], 'Message-ID:')), 260)
Compose 4 tackles the unique id extraction:
substring(outputs('Compose_2'), add(indexOf(outputs('Compose_2'), '<'),1), sub(sub(indexOf(outputs('Compose_2'), '>'), indexOf(outputs('Compose_2'), '<')), 1))
The email document name and file name values are the unique id extracted in previous step (output of Compose 4).
Other fields are specified like this:
"@{outputs('Compose_4')}"
"@{outputs('Compose_4')}.eml"
"@{base64(body('Export_email'))}"
"@{items('Apply_to_each_3')?['Email']?['Subject']}"
"@{items('Apply_to_each_3')?['Email']?['From']}"
"@{items('Apply_to_each_3')?['Email']?['To']}"
"@{items('Apply_to_each_3')?['Email']?['Cc']}"
"@{items('Apply_to_each_3')?['Email']?['DateTimeReceived']}"
The flow is ready to run. It needs to be deployed for every user whose inbox must be monitored.
Name is a name of project, taken from the outer loop. Here is Append to array's in json with the context for reference:
Sometimes it is hard to 'decode' flow decorated variables.
Email.ID is this expression:
Another one, had does the Email.ID get setup?
hello, under the "Append to array variable" portion, where does the variable 'name' come from? I have one that says Output, but not "name".