> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.meetgail.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.meetgail.com/_mcp/server.

# Process a Daily CSV

> Build a workflow that pulls a partner's daily lead file from SFTP every morning and adds each row as a contact.

Plenty of partners drop a file of new leads on a server every night. This guide
builds a workflow that runs each morning, downloads that file, reads it row by
row, and adds every lead as a contact, all on its own.

The workflow has these steps, running top to bottom:

Start, then SFTP Download, then Parse CSV, then a For Each loop that runs Add
Contact for every row, then End.

![The Process Daily CSV workflow on the builder canvas: Start, SFTP Download, Parse CSV, a For Each loop containing Add Contact, End.](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/gail.docs.buildwithfern.com/0a549c62dbffb87887b574ef656ed9e5a7b436cf8b1bd03664a4632c19a9e7a5/docs/pages/platform/workflows/assets/guide-csv-canvas.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260823%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260823T225010Z&X-Amz-Expires=604800&X-Amz-Signature=6cf8f0fb38c36a2e8eff98f2710d7e638e019a11aaf4a5e4438f418652dce18e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

## What each step does

* **Start** is a scheduled trigger that runs the workflow once a day.
* **SFTP Download** connects to the partner's server and pulls down the file.
* **Parse CSV** turns the downloaded file into a list of rows.
* **For Each** loops over the rows and runs Add Contact for each one.
* **End** finishes the run.

## Build it step by step

### Set the Start step to run on a schedule

Configure the Start step as a scheduled trigger so the workflow runs every
morning without anyone kicking it off. See
[Triggers & Webhooks](/platform/workflows/core-concepts/triggers-webhooks) for how
triggers are set up.

### Add the SFTP Download step

Add an [SFTP Download](/platform/workflows/node-reference/integrations/sftp/sftp-download)
step below Start and point it at the partner's server:

| Field                  | What to enter                          |
| ---------------------- | -------------------------------------- |
| Host                   | `sftp.example.com`                     |
| Port                   | `22`                                   |
| Username               | `gail-imports`                         |
| Remote Path            | `/exports/leads-daily.csv`             |
| Password / Private Key | Choose a stored secret from the picker |

Never type a password or private key into a step. The credential fields on
SFTP Download show a secret picker, not a text box, so the value comes from a
stored secret and stays out of the workflow. Set one up first. See
[Secrets](/platform/workflows/core-concepts/secrets).

![The SFTP Download settings panel: host, port, username, remote path, and secret pickers for the password and private key.](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/gail.docs.buildwithfern.com/b801630192e945e0fb0ec579168b9ee70e4d6df6ebdafe7e8c001b882c5364d4/docs/pages/platform/workflows/assets/guide-csv-sftp-download.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260823%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260823T225010Z&X-Amz-Expires=604800&X-Amz-Signature=36a2ac1ababc21e920ee4b153d4d040f98ce5b687a3ece2df7c710a3b2e84036&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

### Add the Parse CSV step

Add a [Parse CSV](/platform/workflows/node-reference/files-and-documents/parse-csv) step
below SFTP Download. For the **CSV file** field, point it at the file the
download produced:

```
sftp-download.data.file
```

Turn the "first row is a header" option on, so the column names come from the
top row of the file.

![The Parse CSV settings panel: the CSV file set to sftp-download.data.file with first-row-is-a-header turned on.](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/gail.docs.buildwithfern.com/62b86ad74ae47b162c1816c4fb8c588562af90dd01ba9da79db33179d80c3acc/docs/pages/platform/workflows/assets/guide-csv-parse-csv.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260823%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260823T225010Z&X-Amz-Expires=604800&X-Amz-Signature=356b04d10003626b077ee721e5aa0e24b2688fb506741d5a8d2ff95546960d71&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

### Add the For Each loop

Add a [For Each](/platform/workflows/node-reference/logic-and-flow/for-each) step below
Parse CSV. For the **Collection** field, point it at the parsed rows:

```
parse-csv.data.rows
```

### Add Add Contact inside the loop

Inside the For Each loop, add an
[Add Contact](/platform/workflows/node-reference/gail/add-contact) step. Inside a loop,
the current row is available as `_each_.item`, so read each column from it:

| Field         | Expression               |
| ------------- | ------------------------ |
| First name    | `_each_.item.first_name` |
| Last name     | `_each_.item.last_name`  |
| Email         | `_each_.item.email`      |
| Phone number  | `_each_.item.phone`      |
| Business name | `_each_.item.business`   |

The column names after `item` match the header row of your file, so adjust
them to whatever the partner's file uses.

### Connect the steps and save

Connect Start to SFTP Download to Parse CSV to For Each to End, then save.

![The For Each settings panel: the collection set to parse-csv.data.rows with the collect-results option.](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/gail.docs.buildwithfern.com/b9c25d0f82b5e1eace422996e15fef110081881a32a3369d3115f41d2645e3fc/docs/pages/platform/workflows/assets/guide-csv-for-each.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260823%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260823T225010Z&X-Amz-Expires=604800&X-Amz-Signature=1339233e95f84cd49978493099c45957f90ca3439efb654da6ece12ef9f8bc79&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

For Each has a "collect results" option that gathers every pass's output into a
list. Leave it off for large loops like a daily import; a long list of results
can grow large fast, and each pass is still visible in the run history either
way.

## What to try next

#### [Loop Over a CSV](/platform/workflows/guides-and-recipes/recipes/loop-over-a-csv)

The short version of the parse-then-loop pattern.

#### [Branch on a Value](/platform/workflows/guides-and-recipes/recipes/branch-on-a-value)

Skip or route rows that do not meet a condition.

#### [Append to Google Sheets](/platform/workflows/guides-and-recipes/recipes/append-to-google-sheets)

Log each imported row to a spreadsheet as it goes.

#### [Welcome New Leads](/platform/workflows/guides-and-recipes/guides/welcome-new-leads)

Greet each new lead the moment they arrive.