# Getting Started: Borrower Campaigns with Bulk Sender

This tutorial will guide you through creating your first borrower campaign with bulk sender functionality. Bulk senders allow you to automatically send communications (emails, text messages, etc.) to borrowers based on query results from Redash.

## Overview

Borrower campaigns with bulk senders enable you to:

- Query your borrower data using Redash
- Automatically send communications to borrowers matching your query criteria
- Schedule campaigns to run automatically on a recurring basis
- Track campaign execution and results


The workflow consists of three main components:

1. **Redash Query**: Defines which borrowers to target
2. **Bulk Sender**: Configures what message to send and how
3. **Campaign**: Ties everything together and optionally schedules execution


## Prerequisites

Before you begin, ensure you have:

- Access to Redash with a query that returns borrower data
- A Redash API key (found at `https://{YOUR_REDASH_DOMAIN}/users/me`)


## Step 1: Create a Redash Query

Your Redash query must return at least a `borrowerId` column. You can use either:

- Public IDs (e.g., `BO-XXXX-XXXX`)
- Internal database IDs (integers)


### Example Query

Specify a SQL query targeting the borrowers you care about:


```sql
select distinct 
  lp.person_id as "borrowerId",
  l.id as "loanId"
from
  loans l
join loan_people lp on lp.loan_id = l.id
join people p on lp.person_id = p.id
where
  l.status = 'Active' 
  and {other properties of loans or borrowers you care about};
```

**Important**: The column must be named exactly `borrowerId` (case-sensitive).

### Query Result Format

For basic bulk sending, your query result could look like:


```
borrowerId,loanId
100,200
103,205
...
```

### Custom Context Variables

You can pass custom data to your message templates by prefixing columns with `context.`. For example:


```sql
SELECT 
    b.id AS "borrowerId",
    l.id AS "loanId",
    l.total_amount AS "context.loanAmount",
    l.due_date AS "context.dueDate"
FROM borrowers b
JOIN loans l ON l.borrower_id = b.id
WHERE l.status = 'overdue'
```

This will make `loanAmount` and `dueDate` available in your message templates via the `context` object.

## Step 2: Create a Bulk Sender

A bulk sender defines:

- **Interaction Subject**: The type of message (e.g., `paymentDueDateReminder`, `loanOverdueFirstNotice`)
- **Interaction Theme**: The category of interaction (e.g., `opsServicing`, `collections`)
- **Interaction Channel**: The delivery method (e.g., `email`, `text`, `mail`)


**Note**: Only `mail`, `text`, and `email` channels are compatible with the campaigns system.

### Example: Create a Payment Reminder Bulk Sender


```bash
POST /api/campaign-workers/bulk-senders
Content-Type: application/json

{
  "interactionSubject": "paymentDueDateReminder",
  "interactionTheme": "opsServicing",
  "interactionChannel": "email",
}
```

### Response


```json
{
  "data": {
    "id": "BS-XXXX-XXXX",
    "interactionSubject": "paymentDueDateReminder",
    "interactionTheme": "opsServicing",
    "interactionChannel": "email",
    "createdAt": "2024-01-15T10:30:00Z"
  }
}
```

Save the `id` field (e.g., `BS-XXXX-XXXX`) - you'll need it when creating the campaign.

### Available Interaction Subjects

Some common interaction subjects that work well with bulk senders:

- `paymentDueDateReminder` - Remind borrowers of upcoming payments
- `loanOverdueFirstNotice` - First notice for overdue loans
- `loanOverdueSecondNotice` - Second notice for overdue loans
- `autopayPaymentReminder` - Remind borrowers of upcoming autopay payments
- `autopayEnableReminder` - Encourage borrowers to enable autopay
- `loginFirstPaymentReminder` - Remind borrowers who haven't logged in about their first payment
- `cardExpiresReminder` - Notify borrowers about expiring payment cards


Each subject has specific query requirements and automatically calculated context variables.

## Step 3: Create a Campaign

Now you'll create a campaign that ties your Redash query to your bulk sender.

### Example: Create a Campaign


```bash
POST /api/campaigns
Content-Type: application/json

{
  "redashQueryUrl": "https://your-redash-domain.com/queries/123",
  "redashApiKey": "your-redash-api-key",
  "bulkSenderId": "BS-XXXX-XXXX",
  "externalId": "weekly-payment-reminders",
  "schedule": "P P * * mon"
}
```

### Campaign Parameters

- **`redashQueryUrl`**: The full URL to your Redash query (format: `https://{DOMAIN}/queries/{QUERY_ID}`)
- **`redashApiKey`**: Your Redash API key
- **`bulkSenderId`**: The ID of the bulk sender created in Step 2
- **`externalId`**: (Optional) Your own identifier for this campaign
- **`schedule`**: (Optional) Cron schedule for automatic execution


### Schedule Format

The schedule uses "Peachy crontab" format where `P` means "Peach decides":

- Minute and hour fields must be `P`
- Day, month, and day-of-week can be standard cron values


Examples:

- `P P * * mon` - Every Monday
- `P P 1,15 * *` - 1st and 15th of every month
- `P P * * wed` - Every Wednesday
- `P P * 10 *` - Every day in October


If `schedule` is `null`, the campaign must be run manually.

### Response


```json
{
  "data": {
    "id": "BC-XXXX-XXXX",
    "redashQueryUrl": "https://your-redash-domain.com/queries/123",
    "bulkSenderId": "BS-XXXX-XXXX",
    "externalId": "weekly-payment-reminders",
    "schedule": "P P * * mon",
    "createdAt": "2024-01-15T10:35:00Z"
  }
}
```

## Step 4: Run a Campaign

If you didn't set a schedule, or want to run the campaign immediately, you can trigger it manually.

### Manual Campaign Run


```bash
POST /api/campaigns/BC-XXXX-XXXX/runs
Content-Type: application/json

{}
```

### Override Query Results (Optional)

For testing, you can override the Redash query with custom data:


```bash
POST /api/campaigns/BC-XXXX-XXXX/runs
Content-Type: application/json

{
  "queryResultsOverride": [
    {
      "borrowerId": "BO-YYYY-YYYY",
      "loanId": "LO-ZZZZ-ZZZZ"
    },
    {
      "borrowerId": "BO-AAAA-AAAA",
      "loanId": "LO-BBBB-BBBB"
    }
  ]
}
```

### Response


```json
{
  "data": {
    "id": "RN-XXXX-XXXX",
    "borrowerCampaignId": "BC-XXXX-XXXX",
    "status": "initializing",
    "availableFiles": []
  }
}
```

## Step 5: Monitor Campaign Runs

Check the status of your campaign run:


```bash
GET /api/campaigns/BC-XXXX-XXXX/runs/RN-XXXX-XXXX
```

### Run Statuses

- `initializing` - Campaign run is being set up
- `processing` - Campaign is actively running
- `success` - Campaign completed successfully
- `failed` - Campaign encountered an error


### Download Campaign Results

Once a campaign run completes, you can download various files:


```bash
GET /api/campaigns/BC-XXXX-XXXX/runs/RN-XXXX-XXXX/download?file=all
```

Available files:

- `query_results.csv` - Raw query results from Redash
- `campaign_input.json` - Parsed query results used as input
- `contacts.csv` - Exported contacts (if using contact exporter)
- `log.txt` - Execution log


Use `file=all` to download all files as a ZIP, or specify a specific file.

## Best Practices

### 1. Query Optimization

- Keep your queries focused and efficient
- Use appropriate WHERE clauses to limit results
- Consider adding indexes on frequently queried columns


### 2. Testing

- Always test with `queryResultsOverride` first using a small dataset
- Verify message templates work correctly with your context variables
  - Campaign bulk send messages are sent with `strictUndefined: true` meaning there can be NO undefined variables or the send will fail. (This is not the default setting when using the `/send` endpoint.)
- Check campaign run logs for any errors.
  - It's important to *check the logs not just the status* of the campaign run. This is because some or all of the message sends might fail even while the campaign run itself is considered a success.


### 3. Scheduling

- Start with manual runs to verify everything works
- Note that the schedule syntax is ["crontab" like](https://en.wikipedia.org/wiki/Cron). You can specify a crontab except the first two parts must be `P P` this indicates that the specific time of execution on the given day is at Peach's discretion.


### 4. Context Variables

- Use `context.*` columns in your query to pass custom data to templates
- Be aware that some interaction subjects automatically calculate context variables
- Ensure all required context variables are present or calculated


## Troubleshooting

### Campaign Run Fails

1. Check the campaign run status and logs:

```bash
GET /api/campaigns/{campaignId}/runs/{runId}
GET /api/campaigns/{campaignId}/runs/{runId}/download?file=log.txt
```


### Messages Not Sending

1. Verify the bulk sender configuration:
  - Check that `interactionSubject`, `interactionTheme`, and `interactionChannel` are valid
  - Ensure message templates exist for the specified subject/theme/channel combination
2. Check borrower eligibility:
  - Borrowers must have valid contact information for the specified channel
  - Compliance checks may prevent sending (Do Not Interact, frequency limits, etc.)


### Query Results Issues

1. Verify your Redash query:
  - Test the query directly in Redash
  - Ensure column names match exactly (case-sensitive)
  - Check that IDs are valid (either public IDs or internal integer IDs)
2. Check query result format:
  - Minimum required: `borrowerId` column
  - Additional columns depend on the interaction subject
  - Use `context.*` prefix for custom context variables


## Next steps

- **[Editing Templates](/communications/editing-templates)** — Customize the templates used by bulk campaigns and one-off sends.
- **[Communications overview](/communications)** — Index of communications channels and tools.