Monthly Bulk Contact Import from Excel
Monthly flow reads a contact spreadsheet from Excel Online, converts to CSV format, creates a bulk insert job, uploads the data via UploadJobData, closes the job, and posts the import summary to Teams.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
A scheduled cloud flow that runs monthly, reads a contact roster from an Excel Online table, converts it to CSV, and bulk-inserts the rows into Salesforce using the Bulk API 2.0 three-step dance (CreateJobV2 -> UploadJobData -> CloseJob), polls the job until it terminates, then posts a success or failure summary to a Microsoft Teams channel.
The flow is designed to handle thousands of Contact rows per run without hitting the Salesforce REST row-by-row governor limits. Bulk API 2.0 runs the insert asynchronously on the Salesforce side, which is why the flow polls GetJobInfo rather than waiting on a synchronous response.
The trigger is a monthly Recurrence: runs once every month at 02:00 Eastern Time on the 1st of the month.
Use Case
Use this pattern whenever you need to push a large batch of Contact records (or any Salesforce object) from a Microsoft 365 source into Salesforce on a schedule, without burning REST API calls one row at a time. Bulk API 2.0 handles the heavy lifting asynchronously and the Teams summary closes the loop for the data steward.
The flow is ideal for teams that:
- Operations teams that maintain a master contact roster in Excel and sync it to Salesforce monthly
- IT admins replacing legacy row-by-row Salesforce upsert flows that hit API governor limits
- Data-management teams that need an auditable, scheduled bulk-load with a Teams notification trail
- Salesforce admins who want a reusable Bulk API 2.0 three-step pattern (CreateJobV2 / UploadJobData / CloseJob) they can adapt to other objects
Flow Architecture
Recurrence (monthly)
RecurrenceRuns once every month at 02:00 Eastern Time on the 1st of the month. Frequency=Month, Interval=1, monthDays=[1], hours=[2], minutes=[0].
Initialize variables
Initialize variable (x7)Seven chained Initialize variable actions: varExcelDriveId, varExcelFileId, varExcelTableName, varImportOperation (insert/update/upsert/delete), varTeamsGroupId, varTeamsChannelId (all from environment variables) and varContactCount (integer, defaults to 0; set later from the Select output length).
List rows present in a table
Excel Online (Business) - GetItemsReads every row from the Excel table identified by varExcelDriveId / varExcelFileId / varExcelTableName.
Compose CSV Header
ComposeStatic header string: `FirstName,LastName,Email,Phone,AccountId`.
Select rows -> CSV lines
Select (Data Operations)Maps each Excel row to a comma-joined CSV line using `concat(coalesce(item()?['FirstName'],''), ',', coalesce(item()?['LastName'],''), ...)` so empty cells become empty CSV fields instead of `null` tokens.
Set varContactCount
Set variableSets varContactCount = `length(body('Select_Contact_Rows_To_CSV_Lines'))` so the count can be reported in the Teams notification.
Compose CSV Payload
ComposeBuilds the full CSV body: `concat(outputs('Compose_CSV_Header'), decodeUriComponent('%0A'), join(body('Select_Contact_Rows_To_CSV_Lines'), decodeUriComponent('%0A')))`. `%0A` is LF, matching the bulk job's declared lineEnding.
Create Salesforce Bulk Job
Salesforce - CreateJobV2Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_CF280_ExcelDriveId | String | <configure> | OneDrive / SharePoint drive ID containing the contact workbook (e.g. `b!abc...`). Set in the target environment before activating the flow. |
| flowlibs_CF280_ExcelFileId | String | <configure> | File (item) ID of the Excel workbook holding the Contacts table. |
| flowlibs_CF280_ExcelTableName | String | Contacts | Named Excel table inside the workbook that holds the contact rows. Must expose the columns FirstName, LastName, Email, Phone, AccountId (case-sensitive ODATA field names). |
| flowlibs_CF280_ImportOperation | String | insert | Salesforce Bulk API 2.0 operation. One of `insert`, `update`, `upsert`, or `delete`. The same flow shape supports all four because only the operation parameter varies. |
| flowlibs_TeamsGroupId | String | <configure> | Microsoft Teams team (group) ID for the notification channel. Shared FlowLibs env var, reused across flows. |
| flowlibs_TeamsChannelId | String | <configure> | Microsoft Teams channel ID within the above team (looks like `19:abc...@thread.tacv2`). Shared FlowLibs env var. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Excel Online (Business) | shared_excelonlinebusiness | GetItems (List rows present in a table) |
| Salesforce | shared_salesforce | CreateJobV2 (Create bulk job V2 (Contact)) UploadJobData (Upload CSV payload to the job) CloseJob (Close the job with state=UploadComplete) GetJobInfo (Polled inside the Until loop) |
| Microsoft Teams | shared_teams | PostMessageToChannelV3 (Success and failure summary posts) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Change the target Salesforce object
- parameters/object is hard-coded to Contact on the Create_Salesforce_Bulk_Job action because changing the entity changes the column contract and the CSV header. To target a different object (Lead, Account, custom object), update the hard-coded value, swap the CSV header in Compose_CSV_Header, and update the Select map to project the new columns.
- Swap insert for update / upsert / delete without redeploying
- parameters/operation reads from flowlibs_CF280_ImportOperation, so you can change the operation at runtime by updating the env var to insert, update, upsert, or delete. For update and upsert, make sure the CSV includes an Id (or external-id) column - otherwise Salesforce will close the job in Failed state.
- Adjust the schedule
- The Recurrence trigger is set to monthly on the 1st at 02:00 ET. Change frequency/interval/hours/timeZone on the trigger to run weekly, daily, or on a different cadence.
- Tune the polling loop
- The Until loop polls GetJobInfo every 60 seconds with a 30-iteration / PT30M cap. For very large jobs you may need to increase the iteration count or the wait interval; for small jobs you can drop the wait to 15-30 seconds to finish faster.
- Route the Teams notification elsewhere
- Both branches post to the team/channel referenced by flowlibs_TeamsGroupId and flowlibs_TeamsChannelId. Change those env-var values (or point to a different shared connection-reference) to redirect notifications without editing the flow.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01CSV header
Static string composed by Compose_CSV_Header. Column order must match the order projected by Select_Contact_Rows_To_CSV_Lines.
EXPR.02Per-row CSV line (Select map, FirstName column)
Each Excel row is projected to one CSV line. `coalesce(item()?['<col>'],'')` ensures empty cells become empty CSV fields, not `null` tokens.
EXPR.03Full CSV payload
Joins the header and the per-row CSV lines with LF (%0A) separators. Matches the bulk job's declared lineEnding: LF.
EXPR.04Row count for notifications
Stored into varContactCount and surfaced in the Teams success message as `Loaded N Contact rows`.
EXPR.05Bulk Job ID hand-off
Reused across UploadJobData, CloseJob, and the GetJobInfo poll to refer back to the job created in step 1.
EXPR.06Until loop exit condition
Exits the polling loop when the Salesforce bulk job reaches any terminal state. Iteration cap: 30 / PT30M.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.