Add bulk send prototype — script, sample CSV, and demo guide
This commit is contained in:
parent
39982008d3
commit
854d112372
|
|
@ -0,0 +1,77 @@
|
|||
# DocuSign Bulk Send — Demo Guide
|
||||
|
||||
## What is Bulk Send?
|
||||
Bulk Send lets you send one template to **many recipients at once** — each gets their own unique envelope, personalized with their name, email, and custom field values.
|
||||
|
||||
## Files in This Directory
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `bulk_send.py` | Main script — creates bulk list, envelope, and sends |
|
||||
| `recipients.csv` | Sample recipient list (5 demo contacts) |
|
||||
|
||||
---
|
||||
|
||||
## CSV Format
|
||||
|
||||
```csv
|
||||
Name,Email,Company,Title,CustomField1
|
||||
Alice Johnson,alice.johnson@example.com,Acme Corp,CEO,CONTRACT-001
|
||||
```
|
||||
|
||||
| Column | Maps To |
|
||||
|--------|---------|
|
||||
| Name | Recipient display name |
|
||||
| Email | Recipient email address |
|
||||
| Company | Tab value in template (`Company` tab) |
|
||||
| Title | Tab value in template (`Title` tab) |
|
||||
| CustomField1 | Envelope custom field (tracking/reference) |
|
||||
|
||||
---
|
||||
|
||||
## How to Run
|
||||
|
||||
### Dry Run (safe — no emails sent)
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
python3 bulk-send/bulk_send.py \
|
||||
--template-id <YOUR_TEMPLATE_ID> \
|
||||
--csv bulk-send/recipients.csv \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
### Live Send
|
||||
```bash
|
||||
python3 bulk-send/bulk_send.py \
|
||||
--template-id <YOUR_TEMPLATE_ID> \
|
||||
--csv bulk-send/recipients.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How It Works (3 API Calls)
|
||||
|
||||
```
|
||||
1. POST /bulk_send_lists → Upload recipient list → get bulk_list_id
|
||||
2. POST /envelopes → Create draft envelope from template → get envelope_id
|
||||
3. POST /bulk_send_lists/{id}/send → Trigger send → get batch_id
|
||||
```
|
||||
|
||||
Each recipient gets:
|
||||
- Their own envelope
|
||||
- Pre-filled tabs (name, company, title)
|
||||
- Unique signing link via email
|
||||
|
||||
---
|
||||
|
||||
## DocuSign API Docs
|
||||
- [Bulk Send Concept](https://developers.docusign.com/docs/esign-rest-api/esign101/concepts/envelopes/bulk-send/)
|
||||
- [How-To: Bulk Send Envelopes](https://developers.docusign.com/docs/esign-rest-api/how-to/bulk-send-envelopes/)
|
||||
|
||||
---
|
||||
|
||||
## Notes for Customer Demo
|
||||
- Uses **demo environment** (demo.docusign.net) — no real emails sent
|
||||
- Template must have a signer role named **"Signer"**
|
||||
- Tabs in template must be named `Company` and `Title` to auto-fill from CSV
|
||||
- Batch status can be checked via: `GET /bulk_send_batch/{batchId}`
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
bulk_send.py — DocuSign Bulk Send Prototype
|
||||
--------------------------------------------
|
||||
Sends a template to multiple recipients from a CSV file using
|
||||
the DocuSign eSignature Bulk Send API (v2.1).
|
||||
|
||||
Usage:
|
||||
python3 bulk_send.py --template-id <TEMPLATE_ID> --csv recipients.csv
|
||||
|
||||
DocuSign API reference:
|
||||
https://developers.docusign.com/docs/esign-rest-api/how-to/bulk-send-envelopes/
|
||||
"""
|
||||
|
||||
import os
|
||||
import csv
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from auth_helper import get_access_token # reuses existing JWT auth
|
||||
|
||||
load_dotenv()
|
||||
|
||||
ACCOUNT_ID = os.getenv("DOCUSIGN_ACCOUNT_ID")
|
||||
BASE_URL = f"https://demo.docusign.net/restapi/v2.1/accounts/{ACCOUNT_ID}"
|
||||
|
||||
|
||||
def load_recipients(csv_path: str) -> list[dict]:
|
||||
"""Load recipients from CSV file."""
|
||||
recipients = []
|
||||
with open(csv_path, newline="") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
recipients.append(row)
|
||||
print(f"✅ Loaded {len(recipients)} recipients from {csv_path}")
|
||||
return recipients
|
||||
|
||||
|
||||
def create_bulk_send_list(token: str, recipients: list[dict], list_name: str = "Bulk Send Demo") -> str:
|
||||
"""Create a bulk send list in DocuSign and return its ID."""
|
||||
url = f"{BASE_URL}/bulk_send_lists"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
bulk_copies = []
|
||||
for r in recipients:
|
||||
bulk_copies.append({
|
||||
"recipients": {
|
||||
"signers": [{
|
||||
"name": r["Name"],
|
||||
"email": r["Email"],
|
||||
"roleName": "Signer", # must match template role name
|
||||
"tabs": {
|
||||
"textTabs": [
|
||||
{"tabLabel": "Company", "value": r.get("Company", "")},
|
||||
{"tabLabel": "Title", "value": r.get("Title", "")},
|
||||
]
|
||||
}
|
||||
}]
|
||||
},
|
||||
"customFields": {
|
||||
"textCustomFields": [{
|
||||
"name": "CustomField1",
|
||||
"value": r.get("CustomField1", "")
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
payload = {
|
||||
"name": list_name,
|
||||
"bulkCopies": bulk_copies
|
||||
}
|
||||
|
||||
resp = requests.post(url, headers=headers, json=payload)
|
||||
resp.raise_for_status()
|
||||
list_id = resp.json()["listId"]
|
||||
print(f"✅ Bulk send list created: {list_id}")
|
||||
return list_id
|
||||
|
||||
|
||||
def create_envelope_from_template(token: str, template_id: str, bulk_list_id: str) -> str:
|
||||
"""Create a draft envelope from template, linked to the bulk send list."""
|
||||
url = f"{BASE_URL}/envelopes"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"status": "created", # draft — bulk send will send it
|
||||
"templateId": template_id,
|
||||
"templateRoles": [{
|
||||
"roleName": "Signer",
|
||||
"name": "Bulk Recipient", # placeholder, overridden by bulk list
|
||||
"email": "bulk@placeholder.invalid"
|
||||
}],
|
||||
"customFields": {
|
||||
"textCustomFields": [{
|
||||
"name": "mailingListId",
|
||||
"value": bulk_list_id,
|
||||
"required": "false",
|
||||
"show": "false"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
resp = requests.post(url, headers=headers, json=payload)
|
||||
resp.raise_for_status()
|
||||
envelope_id = resp.json()["envelopeId"]
|
||||
print(f"✅ Draft envelope created: {envelope_id}")
|
||||
return envelope_id
|
||||
|
||||
|
||||
def send_bulk(token: str, envelope_id: str, bulk_list_id: str) -> dict:
|
||||
"""Trigger the bulk send."""
|
||||
url = f"{BASE_URL}/bulk_send_lists/{bulk_list_id}/send"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
payload = {"envelopeOrTemplateId": envelope_id}
|
||||
|
||||
resp = requests.post(url, headers=headers, json=payload)
|
||||
resp.raise_for_status()
|
||||
result = resp.json()
|
||||
print(f"✅ Bulk send triggered!")
|
||||
print(f" Batch ID: {result.get('batchId')}")
|
||||
print(f" Queued: {result.get('queued')}")
|
||||
print(f" Envelopes: {result.get('totalEnvelopes')}")
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="DocuSign Bulk Send Prototype")
|
||||
parser.add_argument("--template-id", required=True, help="DocuSign template ID to send")
|
||||
parser.add_argument("--csv", default="bulk-send/recipients.csv", help="Path to recipients CSV")
|
||||
parser.add_argument("--list-name", default="Bulk Send Demo", help="Name for the bulk send list")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Create list + envelope but don't send")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("\n🚀 DocuSign Bulk Send Prototype")
|
||||
print("================================")
|
||||
|
||||
token = get_access_token()
|
||||
recipients = load_recipients(args.csv)
|
||||
bulk_list_id = create_bulk_send_list(token, recipients, args.list_name)
|
||||
envelope_id = create_envelope_from_template(token, args.template_id, bulk_list_id)
|
||||
|
||||
if args.dry_run:
|
||||
print("\n⚠️ Dry run — skipping actual send.")
|
||||
print(f" Bulk List ID: {bulk_list_id}")
|
||||
print(f" Envelope ID: {envelope_id}")
|
||||
else:
|
||||
result = send_bulk(token, envelope_id, bulk_list_id)
|
||||
print(f"\n✅ Done! Batch ID: {result.get('batchId')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
Name,Email,Company,Title,CustomField1
|
||||
Alice Johnson,alice.johnson@example.com,Acme Corp,CEO,CONTRACT-001
|
||||
Bob Smith,bob.smith@example.com,Globex Inc,CFO,CONTRACT-002
|
||||
Carol White,carol.white@example.com,Initech Ltd,COO,CONTRACT-003
|
||||
David Lee,david.lee@example.com,Umbrella Co,VP Sales,CONTRACT-004
|
||||
Emma Davis,emma.davis@example.com,Stark Industries,Director,CONTRACT-005
|
||||
|
Loading…
Reference in New Issue