#!/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 --csv recipients.csv DocuSign API reference: https://developers.docusign.com/docs/esign-rest-api/how-to/bulk-send-envelopes/ """ import os import sys import csv import json import argparse import requests from dotenv import load_dotenv sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) from docusign_auth import get_access_token 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()