Collating differences between two inputs and then creating an email

I am trying to compare data from two programs (google sheet snad free agent.

I have collected both sets of data

Now I need to compare one list to another and output all the anomolies into one email that asks for manual intervention to check.

I have

but the output is only the first entry or collection.

The filter

I am not sure if it is only comparing the first collection to the first record or comparing them all and I need support on compiling all mismatches and naming them in an email.

{
    "name": "Integration FreeAgent, Google Sheets",
    "flow": [
        {
            "id": 1,
            "module": "freeagent:ListContacts",
            "version": 1,
            "parameters": {
                "__IMTCONN__": 520270
            },
            "mapper": {
                "sort": "name",
                "view": "active",
                "limit": "1000"
            },
            "metadata": {
                "designer": {
                    "x": 0,
                    "y": 0
                },
                "restore": {
                    "expect": {
                        "sort": {
                            "mode": "chose",
                            "label": "Name"
                        },
                        "view": {
                            "mode": "chose",
                            "label": "Active Contacts"
                        }
                    },
                    "parameters": {
                        "__IMTCONN__": {
                            "data": {
                                "scoped": "true",
                                "connection": "freeagent"
                            },
                            "label": "My FreeAgent connection 1 (XXXXXXXXXXXXXxx)"
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "__IMTCONN__",
                        "type": "account:freeagent",
                        "label": "Connection",
                        "required": true
                    }
                ],
                "expect": [
                    {
                        "name": "limit",
                        "type": "uinteger",
                        "label": "Limit"
                    },
                    {
                        "name": "view",
                        "type": "select",
                        "label": "View",
                        "validate": {
                            "enum": [
                                "all",
                                "active",
                                "clients",
                                "suppliers",
                                "active_projects",
                                "completed_projects",
                                "open_clients",
                                "open_suppliers",
                                "hidden"
                            ]
                        }
                    },
                    {
                        "name": "sort",
                        "type": "select",
                        "label": "Sort",
                        "validate": {
                            "enum": [
                                "name",
                                "created_at",
                                "updated_at"
                            ]
                        }
                    }
                ]
            }
        },
        {
            "id": 2,
            "module": "builtin:BasicAggregator",
            "version": 1,
            "parameters": {
                "feeder": 1
            },
            "mapper": {
                "first_name": "{{1.first_name}}",
                "last_name": "{{1.last_name}}"
            },
            "metadata": {
                "designer": {
                    "x": 300,
                    "y": 0
                },
                "restore": {
                    "extra": {
                        "feeder": {
                            "label": "FreeAgent - List Contacts [1]"
                        },
                        "target": {
                            "label": "Custom"
                        }
                    }
                },
                "expect": [
                    {
                        "name": "__IMTTARGET__",
                        "type": "collection",
                        "label": "Array",
                        "spec": []
                    }
                ]
            }
        },
        {
            "id": 3,
            "module": "google-sheets:filterRowsAdvanced",
            "version": 2,
            "parameters": {
                "__IMTCONN__": 12956187
            },
            "mapper": {
                "select": "list",
                "tq": "where E = \"{{formatDate(now; \"MMMM\")}}\"",
                "from": "drive",
                "spreadsheetId": "/1520T1tiKkQSJLZNDmOytLz5xTsEbt9zBa2xGsJZUwX4",
                "sheetId": 655284133
            },
            "metadata": {
                "designer": {
                    "x": 600,
                    "y": 0
                },
                "restore": {
                    "parameters": {
                        "__IMTCONN__": {
                            "label": "XXXXXXXXXXXXXXXXXXXXXXXX",
                            "data": {
                                "scoped": "true",
                                "connection": "google"
                            }
                        }
                    },
                    "expect": {
                        "select": {
                            "label": "Search by path"
                        },
                        "from": {
                            "label": "My Drive"
                        },
                        "spreadsheetId": {
                            "path": [
                                "Untitled form (Responses)"
                            ]
                        },
                        "sheetId": {
                            "label": "Form responses 1"
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "__IMTCONN__",
                        "type": "account:google",
                        "label": "Connection",
                        "required": true
                    }
                ],
                "expect": [
                    {
                        "name": "select",
                        "type": "select",
                        "label": "Search Method",
                        "required": true,
                        "validate": {
                            "enum": [
                                "list",
                                "fromAll",
                                "map"
                            ]
                        }
                    },
                    {
                        "name": "tq",
                        "type": "text",
                        "label": "Query",
                        "required": true
                    },
                    {
                        "name": "limit",
                        "type": "uinteger",
                        "label": "Limit"
                    },
                    {
                        "name": "from",
                        "type": "select",
                        "label": "Drive",
                        "required": true,
                        "validate": {
                            "enum": [
                                "drive",
                                "share",
                                "team"
                            ]
                        }
                    },
                    {
                        "name": "spreadsheetId",
                        "type": "file",
                        "label": "Spreadsheet ID",
                        "required": true
                    },
                    {
                        "name": "sheetId",
                        "type": "select",
                        "label": "Sheet ID",
                        "required": true
                    }
                ],
                "interface": [
                    {
                        "name": "__IMTLENGTH__",
                        "label": "Total number of bundles",
                        "type": "uinteger"
                    },
                    {
                        "name": "__IMTINDEX__",
                        "label": "Bundle order position",
                        "type": "uinteger"
                    },
                    {
                        "name": "0",
                        "label": "Timestamp (A)",
                        "type": "text"
                    },
                    {
                        "name": "1",
                        "label": "Parent Name (FIRST AND LAST) (this must be exactly as entered into your registration form) (B)",
                        "type": "text"
                    },
                    {
                        "name": "2",
                        "label": "Child Name (how you want to appear on the invoice) (C)",
                        "type": "text"
                    },
                    {
                        "name": "3",
                        "label": "Date (D)",
                        "type": "text"
                    },
                    {
                        "name": "4",
                        "label": "Month (E)",
                        "type": "text"
                    },
                    {
                        "name": "5",
                        "label": "Sessions (F)",
                        "type": "number"
                    },
                    {
                        "name": "6",
                        "label": "Child name 2 (G)",
                        "type": "text"
                    },
                    {
                        "name": "7",
                        "label": "Date Child 2 (H)",
                        "type": "text"
                    },
                    {
                        "name": "8",
                        "label": "Month Child 2 (I)",
                        "type": "text"
                    },
                    {
                        "name": "9",
                        "label": "Sess Child 2 (J)",
                        "type": "number"
                    }
                ]
            }
        },
        {
            "id": 4,
            "module": "builtin:BasicAggregator",
            "version": 1,
            "parameters": {
                "feeder": 3
            },
            "mapper": {
                "1": "{{3.`1`}}"
            },
            "metadata": {
                "designer": {
                    "x": 900,
                    "y": 0
                },
                "restore": {
                    "expect": {
                        "__IMTTARGET__": {
                            "collapsed": true
                        }
                    },
                    "extra": {
                        "feeder": {
                            "label": "Google Sheets - Search Rows (Advanced) [3]"
                        },
                        "target": {
                            "label": "Custom"
                        }
                    }
                },
                "expect": [
                    {
                        "name": "__IMTTARGET__",
                        "type": "collection",
                        "label": "Array",
                        "spec": []
                    }
                ],
                "advanced": true
            }
        },
        {
            "id": 6,
            "module": "builtin:BasicFeeder",
            "version": 1,
            "parameters": {},
            "filter": {
                "name": "",
                "conditions": [
                    [
                        {
                            "a": "{{4.array[].`1`}}",
                            "o": "text:notequal",
                            "b": "{{2.array[].first_name}} {{2.array[].last_name}}"
                        }
                    ]
                ]
            },
            "mapper": {
                "array": "{{4.array[].`1`}}"
            },
            "metadata": {
                "designer": {
                    "x": 1178,
                    "y": -62
                },
                "restore": {
                    "expect": {
                        "array": {
                            "mode": "edit"
                        }
                    }
                },
                "expect": [
                    {
                        "name": "array",
                        "type": "array",
                        "label": "Array",
                        "mode": "edit",
                        "spec": []
                    }
                ]
            }
        },
        {
            "id": 7,
            "module": "microsoft-email:createAndSendAMessage",
            "version": 2,
            "parameters": {
                "__IMTCONN__": 11889084
            },
            "mapper": {
                "subject": "BOOKING SOFTWARE: Name Mismatch",
                "contentType": "text",
                "content": "The following\n\n {{6.value}}\n\nis a name mismatch against the Invoicing Software\n\nPlease either: \n\n1) Change the name in Freeagent to the name in the above name\n\nor\n\n2) Change the booking spreadsheet to the name recorded in Freeagent.",
                "importance": "High",
                "toRecipients": [
                    {
                        "address": "XXXXXXXXXXXXXXXXXXXXXx",
                        "name": "G"
                    }
                ],
                "from": [
                    {
                        "address": "XXXXXXXXXXXXXXXXXXXXX",
                        "name": "PAYMENTS"
                    }
                ],
                "singleValueExtendedProperties": {}
            },
            "metadata": {
                "designer": {
                    "x": 1357,
                    "y": 147
                },
                "restore": {
                    "parameters": {
                        "__IMTCONN__": {
                            "label": "My Microsoft connection (XXXXXXXXXXXXXXXXXXXXXXXX)>)",
                            "data": {
                                "scoped": "true",
                                "connection": "azure"
                            }
                        }
                    },
                    "expect": {
                        "contentType": {
                            "mode": "chose",
                            "label": "Text"
                        },
                        "importance": {
                            "mode": "chose",
                            "label": "High"
                        },
                        "toRecipients": {
                            "mode": "chose",
                            "items": [
                                null
                            ]
                        },
                        "from": {
                            "mode": "chose",
                            "items": [
                                null
                            ]
                        },
                        "ccRecipients": {
                            "mode": "chose"
                        },
                        "bccRecipients": {
                            "mode": "chose"
                        },
                        "replyTo": {
                            "mode": "chose"
                        },
                        "attachments": {
                            "mode": "chose"
                        },
                        "internetMessageHeaders": {
                            "mode": "chose"
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "__IMTCONN__",
                        "type": "account:azure",
                        "label": "Connection",
                        "required": true
                    }
                ],
                "expect": [
                    {
                        "name": "subject",
                        "type": "text",
                        "label": "Subject",
                        "required": true
                    },
                    {
                        "name": "contentType",
                        "type": "select",
                        "label": "Body Content Type",
                        "required": true,
                        "validate": {
                            "enum": [
                                "html",
                                "text"
                            ]
                        }
                    },
                    {
                        "name": "content",
                        "type": "text",
                        "label": "Body Content",
                        "required": true
                    },
                    {
                        "name": "importance",
                        "type": "select",
                        "label": "Importance",
                        "validate": {
                            "enum": [
                                "Low",
                                "Normal",
                                "High"
                            ]
                        }
                    },
                    {
                        "name": "toRecipients",
                        "type": "array",
                        "label": "To Recipients",
                        "required": true,
                        "spec": [
                            {
                                "name": "address",
                                "type": "email",
                                "label": "Email Address",
                                "required": true
                            },
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Name"
                            }
                        ]
                    },
                    {
                        "name": "from",
                        "type": "array",
                        "label": "From",
                        "validate": {
                            "maxItems": 1
                        },
                        "spec": [
                            {
                                "name": "address",
                                "type": "email",
                                "label": "Email Address",
                                "required": true
                            },
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Name"
                            }
                        ]
                    },
                    {
                        "name": "ccRecipients",
                        "type": "array",
                        "label": "CC Recipients",
                        "spec": [
                            {
                                "name": "address",
                                "type": "email",
                                "label": "Email Address",
                                "required": true
                            },
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Name"
                            }
                        ]
                    },
                    {
                        "name": "bccRecipients",
                        "type": "array",
                        "label": "Bcc Recipients",
                        "spec": [
                            {
                                "name": "address",
                                "type": "email",
                                "label": "Email Address",
                                "required": true
                            },
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Name"
                            }
                        ]
                    },
                    {
                        "name": "replyTo",
                        "type": "array",
                        "label": "Reply To",
                        "spec": [
                            {
                                "name": "address",
                                "type": "email",
                                "label": "Email Address",
                                "required": true
                            },
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Name"
                            }
                        ]
                    },
                    {
                        "name": "attachments",
                        "type": "array",
                        "label": "Attachments",
                        "spec": [
                            {
                                "name": "filename",
                                "type": "filename",
                                "label": "File Name",
                                "required": true,
                                "semantic": "file:name"
                            },
                            {
                                "name": "data",
                                "type": "buffer",
                                "label": "Data",
                                "required": true,
                                "semantic": "file:data"
                            }
                        ]
                    },
                    {
                        "name": "internetMessageHeaders",
                        "type": "array",
                        "label": "Internet Message Headers",
                        "spec": [
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Name",
                                "required": true
                            },
                            {
                                "name": "value",
                                "type": "text",
                                "label": "Value",
                                "required": true
                            }
                        ]
                    },
                    {
                        "name": "singleValueExtendedProperties",
                        "type": "collection",
                        "label": "Extended MAPI Properties",
                        "spec": [
                            {
                                "name": "sensitivityLabel",
                                "type": "text",
                                "label": "Sensitivity Label"
                            }
                        ]
                    }
                ],
                "advanced": true
            }
        }
    ],
    "metadata": {
        "instant": false,
        "version": 1,
        "scenario": {
            "roundtrips": 1,
            "maxErrors": 3,
            "autoCommit": true,
            "autoCommitTriggerLast": true,
            "sequential": false,
            "slots": null,
            "confidential": false,
            "dataloss": false,
            "dlq": false,
            "freshVariables": false
        },
        "designer": {
            "orphans": []
        },
        "zone": "eu2.make.com",
        "notes": []
    }
}

So I believe you would have to iterate through both to make this work

Hey there,

A filter will not work here since you need to compare every value in one array against every value in the other. You will either need to iterate them and consume a lot of operations comparing, or write a code snippet that does that and fe d both arrays in a code app to do the comparison.

1 Like

Thanks for that. I have managed to get it working but it is sorting out the wrong criteria- Rather than finding the non customers in the list, it is finding the customers.

Can someone please help

have uploaded the code due to the limit of characters

Integration FreeAgent, Google Sheets.blueprint.json (84.0 KB)

I am very grateful for the help

Hi, I am not sure if you posted before my reply- Can you have a look at the post above and see what I have planned- I think this is the big use of operations you were referring to? I do not have the ability to write a code but would welcome someone to show me how. This would be run once a month and has comparisons of about 100 records.

Sure, if its once a month with a 100 records, I suppose you can spare some operations for it.

What are you comparing exactly between the contacts in FreeAgent and the google sheet?

The input in the sheets has come from customers booking slots for a club via forms. The comparison alerts staff via an email if the sheet does not match the Freeagent billing contacts before it generates the invoices (And errors) (another make integration).

Can you post the JSON outputs of the modules and highlight which items you are trying to compare? Then we can help build an optimized flow to do so.

@Stoyan_Vatov When you say the JSON outputs, is that just the download output button on each module?

There will be a lot of redacting to do if you want the data too. Just so I can get it right

I attach the JSON for the scenario

Integration FreeAgent, Google Sheets FORUM.blueprint (1).json (84.1 KB)

The fields I am trying to compare is in FreeAgent: First name and Last name

to Columns B and C in the Google Sheet

and then send an email using Outlook to an address to notify them that they DO NOT match - ie they have differences and to prompt them to rectify it.

Hi, just wondering if anyone can help - I have been doing the Academy and also lots of problem solving but just seem to get tied up in even more knots.

If I start at the beginning -

I want to take data from Freeagent (I have put this through the array aggregator) - First Name and Last Name

I then want to compare this so a Google Sheet Column A + B (first name and last name) and I only want to find out if there are any names in (First name and last name) the sheet that is not in Free agent.

When I have that data I will then pass it as an email.

So far I have tried putting both into Array’s and comparing with an iterator but when it does match and pass through the no matches, it is passing through every name that Freeagent has compared to the spreadsheet that has not matched rather than doing: If the name in the google sheets is nowhere in Freeagent - copy that name into Outlook, otherwise move onto the next row in Google Sheets.

I hope this is clear but as a newbie in this field, I cannot get this to work.

Whatever happened to if then else or WYSIWYG!

Hello @newbuilder - unless there are some additional requirements to this workflow, I don’t think it needs to be so complicated for your use case here. Try simplifying like this:

Option 1: If you need to send a list of all missing contacts as one email

(e.g. if you’re sending an email to your team about all contacts they need to follow up with)

1. Trigger: FreeBuilder List Contacts (leave this as is)

2. Google Sheets (Search Rows): Put a Google Sheet module immediately after it, and search for a row containing the first and last name, e.g.

(Change the columns to match your actual columns in your Google Sheet)

3. Text aggregator: Add a text aggregator (not array aggregator) after the Google Sheet:

  • Source module: FreeAgent
  • (turn on advanced settings) Row separator: New row
  • Stop processing after an empty aggregator: check this box
  • Text: You can customize this - this will be the content for each missing row

4. Filter: Add a filter between the Google Sheet and the Text Aggregator and set the condition to Row number (from Google Sheets module) does not exist:

This filter will make sure that only the missing contacts (not found in your sheet) get passed to the aggregator.

5. Outlook (Send an Email): Now add your Outlook module at the end, and map the output of the text aggregator in the body, for example:

The result of this will be an email like:

List of all missing contacts:

Missing: Jane Doe (URL: https://example.com/jane-doe)
Missing: John Doe (URL: https://example.com/john-doe)
Missing: Jack Doe (URL: https://example.com/jack-doe)

So the scenario gets all contacts from FreeAgent > for each contact, it tries to find it in the sheet > if contact was not found, store in the aggregator.

Once all contacts have been searched, send the aggregated results to Outlook.

Option 2: If you need to send one email for each missing contact

(For example if you’re sending emails to the contacts individually that their details are not matching)

Same scenario as above, but:

  1. Delete the text aggregator (skip step 3)
  2. Keep the filter between the Sheet and (now) Outlook (step 4)
  3. Change the mapping in the Outlook module to use data from FreeAgent or Google Sheet (step 5)

The result will be one email sent for/to each contact that wasn’t found in the sheet.

Cheers,
Sierra

This is a power of work and yes I went down a rabbit hole.

My question before I get started is.

The Freeagent is the set list - it is what I need to compare TO

The sheet is the data that has manually typed- This is where I want to find the errors and highlight the errors on the sheet?

Is this the opposite to what you have coded?

Hey - it happens to all of us :grinning_face_with_smiling_eyes:

The scenario in my sample basically filters your list of FreeAgent contacts.

You have a list of 500 FA contacts, it will pass all 500 through a filter condition (“not in Sheets”) and provide you with a smaller list of let’s say 100/500 contacts that passed the filter (i.e. weren’t found in Sheets).

What it won’t do: the inverse; it won’t highlight contacts in Sheets that don’t exist in FreeAgent.

If you need a list of all Sheets contacts that are not in Free Agent, then you would need an inverse scenario where Sheets are the trigger and FreeAgent is the search & filter condition.

Let me know if this is unclear or if I misunderstood your use case.

Cheers

The inverse is what I need. Thats the bit my head won’t cope with - every time I do it, it does find them but because it compares every entry every time the duplicates are quite extraordinary

Ah - got it. Okay, try this then:

1. FreeAgent (List Contacts) - leave as is

2. JSON (Aggregate to JSON), configure it like this:

  • Add a new data structure
  • Add new item in specification
  • Set name to fullname
  • Save the structure

  • Map from FreeAgent: [FirstName] [LastName]

(Are there more properties you’re comparing by, or just the first & last name?)

3. JSON (Parse JSON)

Set JSON string to {”names”: [OUTPUT_FROM_AGGREGATOR]}

Run the scenario once with these 3 modules so your “Parse JSON” module would have an output that you can map later.

Then add other modules:

4. Google Sheets (Search Rows) - slightly different, this time you want to fetch all your contacts from the sheet.

You could set the filter to e.g. where First Name & Last Name exist, if needed.

5. Text aggregator

  • Source module: Google Sheets
  • Row separator: New row
  • Stop processing after an empty aggregation: check the box
  • Text: configure as you wish, with data from Google Sheets

6. Filter: Add a filter between Google Sheets and Text aggregator:

The main missing piece - find Sheets rows that don’t exist in FreeAgent.

  • Condition: map([OUTPUT_FROM_PARSE_JSON]; fullname)
  • Operator: scroll all the way down to Array Operators, select Does not contain (case insensitive)
  • Text: map [FirstName] [LastName] from Sheets with a space in between, just like you did in the JSON aggregator in step 2 for FreeAgent

7. Outlook module at the end, with result from the Text aggregator:

Final scenario should look like this:

And what it does is:

  1. Creates a new list of FreeAgent contacts with a combined fullname field (so we only have one field to compare)
  2. For each row in Sheets, check if the name exists in FreeAgent list
  3. If it doesn’t, pass the filter and add to aggregator
  4. In the end send the full list of Google Sheets rows that don’t exist in FreeAgent, to Outlook

Cheers!

2 Likes

Absolutely amazing. It does as I need. One little thing- that probably is too big to look at fixing -

Can the text of mismatches be separated by new rows or breaks

EMail:

ps- still got to get the http link in

You can use HTML to force the line break, and even do some nice formatting if you’d like.

Here are two samples:

1. Basic - separate rows with a line break:

  • Change Row separator to Other
  • Set the separator to <br> (this is a line break in HTML format - accepted in emails)


2. Advanced - format rows as table rows:

  • Reset Row separator to <empty>
  • Copy-paste this whole block in the text field:
<tr>
<td style="padding: 5px; border: 1px solid #ddd;">{{ROW_NUMBER}}</td>
<td style="padding: 5px; border: 1px solid #ddd;">{{FIRST_NAME}}</td>
<td style="padding: 5px; border: 1px solid #ddd;">{{LAST_NAME}}</td>
<td style="padding: 5px; border: 1px solid #ddd;"><a href="https://docs.google.com/spreadsheets/d/{{SPREADSHEET_ID}}/edit?gid={{SHEET_ID}}&range={{ROW_NUMBER}}:{{ROW_NUMBER}}" style="background-color: #0066cc; color: white; padding: 6px 12px; text-decoration: none; border-radius: 3px; display: inline-block;">View</a></td>
</tr>

And replace all the variables inside curly brackets with matching variables from the Sheets module.

For the {{SHEET_ID}}, you’ll need to copy that from your spreadsheet’s URL. It will be a number after gid= in the URL - either 0 or some 8-9 digit number, like gid=XXXXXXXX. The XXXXXXXX is your sheet ID.

The aggregator should look like this in the end:

Update email content:

Then in your email module, copy-paste the below instead of your TEXT variable, and replace {{TEXT_HERE}} with the text aggregator’s output:

<table width="100%" style="max-width: 600px; border-collapse: collapse;">
<tr>
<th style="background-color: #f0f0f0; padding: 5px; text-align: left; border: 1px solid #ddd;">Row #</th>
<th style="background-color: #f0f0f0; padding: 5px; text-align: left; border: 1px solid #ddd;">First Name</th>
<th style="background-color: #f0f0f0; padding: 5px; text-align: left; border: 1px solid #ddd;">Last Name</th>
<th style="background-color: #f0f0f0; padding: 5px; text-align: left; border: 1px solid #ddd;">View Record</th>
</tr>
{{TEXT_HERE}}
</table>

Like this:

End result will look something like this:

Cheers!

1 Like

@SierraV Yur knowledge and skills are immense and the way you make novices feel - that it is a learning opportunity is so humble and reassuring. I am already strarting to use some of the ideas in other automations.

1 Like