> You are reading the Docusign Developer Documentation. For clean Markdown of any page, append .md to the page URL.
>
> Documentation indexes by section:
> - APIs and SDKs: https://docusign.docs.buildwithfern.com/docs/llms.txt
> - Platform 101: https://docusign.docs.buildwithfern.com/platform/llms.txt
> - Extension Apps: https://docusign.docs.buildwithfern.com/extension-apps/llms.txt
> - Docusign CLI: https://docusign.docs.buildwithfern.com/docusign-cli/llms.txt
>
> Authentication (OAuth, JWT Grant, M2M): https://docusign.docs.buildwithfern.com/platform/auth
> Webhooks and Connect: https://docusign.docs.buildwithfern.com/platform/webhooks
>
> OpenAPI specifications: https://docusign.docs.buildwithfern.com/openapi.json
> MCP server: https://docusign.docs.buildwithfern.com/_mcp/server

# How to bulk send envelopes

> Demonstrates how to send envelopes in bulk to multiple recipients

Expanded by eSignature REST API 2.1

This topic demonstrates how to send envelopes in bulk to multiple recipients with the eSignature API. To accomplish the task, you will first create a bulk send recipients list, then create an envelope. From there, you will combine the envelope and bulk list to initiate bulk send.

> **Note**
>
> Run this code example from our [Quickstart](/docs/esign-rest-api/quickstart) or launcher projects: [Bash](https://github.com/docusign/code-examples-bash), [C#](https://github.com/docusign/code-examples-csharp), [Java](https://github.com/docusign/code-examples-java), [Node.js](https://github.com/docusign/code-examples-node), [PHP](https://github.com/docusign/code-examples-php), [PowerShell](https://github.com/docusign/code-examples-powershell), [Python](https://github.com/docusign/code-examples-python), or [Ruby](https://github.com/docusign/code-examples-ruby).

## Required data

Running the code in this how-to requires this data:

| Data element                                | Description                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \{API_ACCOUNT_ID}                           | A [GUID](http://guid.one/) value that identifies your account. This value is automatically generated by Docusign for any account you create. Copy the value from the **API Account ID** field on the [Apps and Keys](https://admindemo.docusign.com/authenticate?goTo=appsAndKeys) page.                                                                                                                             |
| \{BASE_PATH}                                | A string value that forms the root of the URL required to make API calls. The base path value in the example code is set to target the developer environment at https\://\<domain>/restapi Where \<domain> is demo.docusign.net for the developer environment and is the result of the GetUserInfo call in production.                                                                                               |
| \{BULK_LIST_ID}                             | A [GUID](http://guid.one/) value that identifies the bulk list whose data will be retrieved. This value is automatically generated by Docusign when the bulk list is created.                                                                                                                                                                                                                                        |
| Bulk sending enabled for your account       | Your user role needs the **bulkSendingEnabled** permission set to **true** in order to send bulk envelopes. To enable this feature, contact your Account Manager or Business Development representative.                                                                                                                                                                                                             |
| Document to be signed                       | You must provide at least one document to be signed by the signers of this bulk send operation. For this example, you will be sending a base-64 encoded string representing an NDA form.                                                                                                                                                                                                                             |
| Document Visibility features set to **Off** | This how-to requires that [Document Visibility](https://support.docusign.com/s/document-item?language=en_US\&bundleId=gbo1643332197980\&topicId=tmi1578456411160.html&_LANG=enus) be disabled. Disable Document Visibility in the [Sending Settings](https://admindemo.docusign.com/authenticate?goTo=sending) page. Select the Document Visibility drop-down menu to change it to **Off,** and then click **Save.** |
| \{ENVELOPE_ID}                              | A [GUID](http://guid.one/) value that identifies a Docusign [envelope](/docs/esign-rest-api/esign101/concepts/envelopes). This value is automatically generated by Docusign when the envelope is created.                                                                                                                                                                                                            |

## Step 1. Obtain your OAuth token

To make the API calls shown in this how-to, you need a valid OAuth access token. Docusign supports access tokens for three different recommended OAuth grant types: Public Authorization Code Grant, Confidential Authorization Code Grant, and JSON Web Token (JWT) Grant. Choose the OAuth grant type appropriate for your scenario.

> **Note**
>
> See [Confidential Authorization Code Grant](/platform/auth/authcode#confidential-authorization-code-grant) for guidance on your options to get an Confidential Authorization Code Grant  OAuth token.

![Three icons that represent the three methods a user can use to obtain an OAuth token: JSON Web Token (JWT), Public Authorization Code Grant (ACG), and Confidential Authorization Code grant](/_fern-files/_img/64f473773042ee439449a096bf7673292b1585b86a4ccc55f82ba0dda41f61a1.webp)

## Step 2. Construct your API headers

After you have a valid OAuth access token, you are ready to construct the API headers that you will pass to the API calls. Construct your headers like this, and then save them into a file or variable, but make sure to substitute your valid OAuth access token for \{ACCESS_TOKEN}.

**`Bash`**

```bash title="Bash"
declare -a Headers=('--header' "Authorization: Bearer ${ACCESS_TOKEN}" \
					'--header' "Accept: application/json, text/plain, */*" \
					'--header' "Content-Type: application/json;charset=UTF-8" \
					'--header' "Accept-Encoding: gzip, deflate, br" \
					'--header' "Accept-Language: en-US,en;q=0.9")
```

**`C#`**

```csharp title="C#"
var docuSignClient = new DocuSignClient(basePath);
docuSignClient.Configuration.DefaultHeader.Add("Authorization", "Bearer " + accessToken);
var bulkEnvelopesApi = new BulkEnvelopesApi(docuSignClient);
```

**`Java`**

```java title="Java"
ApiClient apiClient = new ApiClient(basePath);
apiClient.addDefaultHeader(HttpHeaders.AUTHORIZATION, BEARER_AUTHENTICATION + userAccessToken);
```

**`Node.js`**

```javascript title="Node.js"
let dsApiClient = new docusign.ApiClient();
dsApiClient.setBasePath(args.basePath);
dsApiClient.addDefaultHeader('Authorization', 'Bearer ' + args.accessToken);
```

**`PHP`**

```php title="PHP"
$config = new Configuration();
$config->setHost($args['base_path']);
$config->addDefaultHeader('Authorization', 'Bearer ' . $args['ds_access_token']);
$this->apiClient = new ApiClient($config);
```

**`PowerShell`**

```text title="PowerShell"
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.add("Authorization", "Bearer $oAuthAccessToken")
$headers.add("Accept", "application/json, text/plain, */*")
$headers.add("Content-Type", "application/json;charset=UTF-8")
$headers.add("Accept-Encoding", "gzip, deflate, br")
$headers.add("Accept-Language", "en-US,en;q=0.9")
```

**`Python`**

```python title="Python"
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
```

**`Ruby`**

```ruby title="Ruby"
configuration = DocuSign_eSign::Configuration.new
configuration.host = args[:base_path]
api_client = DocuSign_eSign::ApiClient.new configuration
construct_api_headers(api_client, args)
```

## Step 3. Submit a bulk list

Your bulk send recipients list begins as a file you create in comma-separated values (CSV) format, holding information for each recipient. To prepare the list for bulk sending, you need to create a JSON representation of your CSV data and add it to your request body before sending it to the API as follows:

1. Create a request body specifying your emails, names, and role names (`roleName`) for each recipient.
2. With your constructed API headers, send the current request body to the eSignature REST API, but make sure to substitute your API account ID for \{API_ACCOUNT_ID}.
3. Extract the `listId` from the response as the \{BULK_LIST_ID}. You will use it again in future steps.

**`Bash`**

```bash title="Bash"
printf \
'{
	"name": "sample.csv",
	"bulkCopies": [{
		"recipients": [{
			"roleName": "signer",
			"name": "'"${SIGNER1_NAME}"'",
			"email": "'"${SIGNER1_EMAIL}"'"
		},
		{
			"roleName": "cc",
			"name": "'"${CC1_NAME}"'",
			"email": "'"${CC1_EMAIL}"'"
		}],
		"customFields": []
	},
  {
		"recipients": [{
			"roleName": "signer",
			"name": "'"${SIGNER2_NAME}"'",
			"email": "'"${SIGNER2_EMAIL}"'"
		},
		{
			"roleName": "cc",
			"name": "'"${CC2_NAME}"'",
			"email": "'"${CC2_EMAIL}"'"
		}],
		"customFields": []
	}]
}	
' >> $request_data					
					
# Make a POST call to the bulk_send_lists endpoint, this will be referenced in future API calls.
# Display the JSON structure of the API response
# Create a temporary file to store the response
echo ""
echo "Posting Bulk Send List"
echo ""
response=$(mktemp /tmp/response-bs.XXXXXX)
Status=$(curl -w '%{http_code}' -i --request POST ${base_path}/v2.1/accounts/${account_id}/bulk_send_lists \
     "${Headers[@]}" \
     --data-binary @${request_data} \
     --output ${response})

if [[ "$Status" -gt "201" ]] ; then
    echo ""
	echo "Posting of the Bulk List has failed"
	echo ""
	cat $response
	exit 0
fi

echo ""
echo "Response:"
cat $response
echo ""

#Obtain the BULK_LIST_ID from the JSON response
BULK_LIST_ID=`cat $response | grep listId | sed 's/.*\"listId\":\"//' | sed 's/\",.*//'`
```

**`C#`**

```csharp title="C#"
var sendingList = MakeBulkSendList(signer1Name, signer1Email, carbonCopy1Name, carbonCopy1Email, signer2Name, signer2Email, carbonCopy2Name, carbonCopy2Email);

var createBulkListResult = bulkEnvelopesApi.CreateBulkSendListWithHttpInfo(accountId, sendingList);
createBulkListResult.Headers.TryGetValue("X-RateLimit-Remaining", out string remaining);
createBulkListResult.Headers.TryGetValue("X-RateLimit-Reset", out string reset);

if (reset != null && remaining != null)
{
    DateTime resetDate = DateTimeOffset.FromUnixTimeSeconds(long.Parse(reset)).UtcDateTime;
    Console.WriteLine("API calls remaining: " + remaining);
    Console.WriteLine("Next Reset: " + resetDate);
}
```

**`Java`**

```java title="Java"
public static BulkSendingList getSendingList(
        String signerName,
        String signerEmail,
        String ccName,
        String ccEmail,
        String signerName2,
        String signerEmail2,
        String ccName2,
        String ccEmail2) {
    List<BulkSendingCopy> copies = List.of(
            createBulkSending(signerName, signerEmail, ccName, ccEmail),
            createBulkSending(signerName2, signerEmail2, ccName2, ccEmail2));
    return new BulkSendingList()
            .name("sample.csv")
            .bulkCopies(copies);
}

public static BulkSendingCopy createBulkSending(
        String signerName,
        String signerEmail,
        String ccName,
        String ccEmail) {
    BulkSendingCopyRecipient recipient1 = new BulkSendingCopyRecipient()
            .name(signerName)
            .email(signerEmail)
            .tabs(Collections.emptyList())
            .roleName(EnvelopeHelpers.SIGNER_ROLE_NAME);
    BulkSendingCopyRecipient recipient2 = new BulkSendingCopyRecipient()
            .name(ccName)
            .email(ccEmail)
            .tabs(Collections.emptyList())
            .roleName(EnvelopeHelpers.CC_ROLE_NAME);
    return new BulkSendingCopy()
            .recipients(List.of(recipient1, recipient2))
            .customFields(Collections.emptyList());
}
```

**`Node.js`**

```javascript title="Node.js"
let bulkList = await bulkEnvelopesApi.createBulkSendList(args.accountId, {
  bulkSendingList: {
    name: 'sample.csv',
    bulkCopies: [
      {
        recipients: recipients(args.list1),
      },
      {
        recipients: recipients(args.list2),
      },
    ],
  },
}, (error, data, response) => {
   const headers = response?.headers;

   const remaining = headers?.['x-ratelimit-remaining'];
   const reset = headers?.['x-ratelimit-reset'];

   if (remaining && reset) {
     const resetInstant = new Date(Number(reset) * 1000);

     console.log(`API calls remaining: ${remaining}`);
     console.log(`Next Reset: ${resetInstant.toISOString()}`);
   }
  });
```

**`PHP`**

```php title="PHP"
$bulk_sending_list = BulkSendEnvelopesService::createBulkSendingList(
    $args["signers"]
);
$bulk_list = $bulk_envelopes_api->createBulkSendListWithHttpInfo(
    $args["account_id"],
    $bulk_sending_list
);
$remaining = $bulk_list[2]['X-RateLimit-Remaining'] ?? null;
$reset = $bulk_list[2]['X-RateLimit-Reset'] ?? null;

if ($remaining !== null && $reset !== null) {
    $resetInstant = (new DateTime())->setTimestamp((int)$reset);
    error_log("API calls remaining: $remaining");
    error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
}
$bulk_list_id = $bulk_list[0]["list_id"];
```

**`PowerShell`**

```text title="PowerShell"
$body = @"
{
	"name": "sample.csv",
	"bulkCopies": [{
		"recipients": [{
			"roleName": "signer",
			"name": "${SIGNER1_NAME}",
			"email": "${SIGNER1_EMAIL}"
		},
		{
			"roleName": "cc",
			"name": "${CC1_NAME}",
			"email": "${CC1_EMAIL}"
		}],
		"customFields": []
	},
{
		"recipients": [{
			"roleName": "signer",
			"name": "${SIGNER2_NAME}",
			"email": "${SIGNER2_EMAIL}"
		},
		{
			"roleName": "cc",
			"name": "${CC2_NAME}",
			"email": "${CC2_EMAIL}"
		}],
		"customFields": []
	}]
}
"@

$uri = "https://demo.docusign.net/restapi/v2.1/accounts/$APIAccountId/bulk_send_lists"

# Make a POST call to the bulk_send_lists endpoint, this will be referenced in future API calls.
# Display the JSON structure of the API response
# Create a temporary file to store the response
try {
	Write-Output @"

        Posting bulk send list
"@
	$response = Invoke-RestMethod -uri $uri -headers $headers -body $body -method POST
	#Obtain the listId from the response
	$listId = $response.listId
	$response.bulkCopies | ConvertTo-Json
}
catch {
	#On failure, display a notification, X-DocuSign-TraceToken, error message, and the command that triggered the error
	Write-Output "Posting of your Bulk List has failed"
	$continue = $false
	$int = 0
	foreach ($header in $_.Exception.Response.Headers) {
		if ($header -eq "X-DocuSign-TraceToken") { Write-Output "TraceToken : " $_.Exception.Response.Headers[$int] }
		$int++
	}
	Write-Output "Error : "$_.ErrorDetails.Message
	Write-Output "Command : "$_.InvocationInfo.Line
}
```

**`Python`**

```python title="Python"
bulk_envelopes_api = BulkEnvelopesApi(api_client)
bulk_sending_list = cls.create_bulk_sending_list(args["signers"])
(bulk_list, status, headers) = bulk_envelopes_api.create_bulk_send_list_with_http_info(
    account_id=args["account_id"],
    bulk_sending_list=bulk_sending_list
)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")

bulk_list_id = bulk_list.list_id
```

**`Ruby`**

```ruby title="Ruby"
bulk_envelopes_api = DocuSign_eSign::BulkEnvelopesApi.new api_client
bulk_sending_list = create_bulk_sending_list(signers)
bulk_list, _status, headers = bulk_envelopes_api.create_bulk_send_list_with_http_info(args[:account_id], bulk_sending_list)

remaining = headers['X-RateLimit-Remaining']
reset = headers['X-RateLimit-Reset']

if remaining && reset
  reset_date = Time.at(reset.to_i).utc
  puts "API calls remaining: #{remaining}"
  puts "Next Reset: #{reset_date}"
end

bulk_list_id = bulk_list.list_id
```

## Step 4. Create an envelope

With your generated API headers, create a request body for an envelope and set the envelope status to `created`. Send a request to the eSignature REST API and extract the envelope ID from the response as the \{ENVELOPE_ID}. You will use it again in steps 5 and 6.

**`Bash`**

```bash title="Bash"
request_data=$(mktemp /tmp/request-bs.XXXXXX)
printf \
'{
	"documents": [{
		"documentBase64": "' >> $request_data
		cat $doc1_base64 >> $request_data
		printf '",
		"name": "Lorem Ipsum",
		"fileExtension": "pdf",
		"documentId": "1"
	}],
	"envelopeIdStamping": "true",
	"emailSubject": "Please sign",
	"recipients": {
		"signers": [{
			"name": "Multi Bulk Recipient::signer",
			"email": "multiBulkRecipients-signer@docusign.com",
			"roleName": "signer",
			"routingOrder": "1",
			"recipientId" : "1",
			"recipientType" : "signer",
			"deliveryMethod" : "Email",
			"status": "created",
			"tabs": {
				"signHereTabs": [{
					"documentId": "1",
					"name": "SignHereTab",
					"pageNumber": "1",
					"recipientId": "1",
					"tabLabel": "SignHereTab",
					"xPosition": "200",
					"yPosition": "160"
				}]}
			}],
		"carbonCopies": [{
			"name": "Multi Bulk Recipient::cc",
			"email": "multiBulkRecipients-cc@docusign.com",
			"roleName": "cc",
			"routingOrder": "2",
			"recipientId" : "2",
			"recipientType" : "cc",
			"deliveryMethod" : "Email",
			"status": "created"
			}]
		},		
	"status": "created"
}	
' >> $request_data

echo ""
echo "Creating a draft envelope."
echo ""
response=$(mktemp /tmp/response-bs.XXXXXX)
Status=$(curl -w '%{http_code}' -i --request POST ${base_path}/v2.1/accounts/${account_id}/envelopes \
     "${Headers[@]}" \
     --data-binary @${request_data} \
     --output ${response})

#If the Status code returned is greater than 201 (OK / Accepted), display an error message along with the API response. 
if [[ "$Status" -gt "201" ]] ; then
    echo ""
	echo "Creation of the draft envelope has failed"
	echo ""
	cat $response
	exit 0
fi

echo ""
echo "Response:"
cat $response
echo ""

#Obtain the envelopeId from the API response.
ENVELOPE_ID=`cat $response | grep envelopeId | sed 's/.*\"envelopeId\":\"//' | sed 's/\",.*//'`
```

**`C#`**

```csharp title="C#"
var envelopeDefinition = new EnvelopeDefinition
{
    Documents = new List<Document>
        {
            new Document
            {
                DocumentBase64 = Convert.ToBase64String(System.IO.File.ReadAllBytes(docDocx)),
                Name = "Lorem Ipsum",
                FileExtension = "pdf",
                DocumentId = "1",
            },
        },
    EnvelopeIdStamping = envelopeIdStamping,
    EmailSubject = emailSubject,
    Status = "created",
    Recipients = new Recipients
    {
        Signers = new List<Signer>
        {
            new Signer
            {
                Name = "Multi Bulk Recipient::signer",
                Email = "multiBulkRecipients-signer@docusign.com",
                RoleName = "signer",
                RoutingOrder = "1",
                Status = "created",
                DeliveryMethod = "Email",
                RecipientId = "1",
                RecipientType = "signer",
                Tabs = new Tabs
                {
                    SignHereTabs = new List<SignHere>
                    {
                        new SignHere
                        {
                            AnchorString = "/sn1/",
                            AnchorUnits = "pixels",
                            AnchorYOffset = "10",
                            AnchorXOffset = "20",
                        },
                    },
                },
            },
        },
        CarbonCopies = new List<CarbonCopy>
        {
            new CarbonCopy
            {
                Name = "Multi Bulk Recipient::cc",
                Email = "multiBulkRecipients-cc@docusign.com",
                RoleName = "cc",
                RoutingOrder = "2",
                Status = "created",
                DeliveryMethod = "Email",
                RecipientId = "2",
                RecipientType = "cc",
            },
        },
    },
};

EnvelopesApi envelopesApi = new EnvelopesApi(docuSignClient);
var envelopeResults = envelopesApi.CreateEnvelope(accountId, envelopeDefinition);
```

**`Java`**

```java title="Java"
EnvelopesApi envelopesApi = new EnvelopesApi(apiClient);
var envelopeResponse = envelopesApi.createEnvelopeWithHttpInfo(accountId, makeEnvelope(),
        envelopesApi.new CreateEnvelopeOptions());
headers = envelopeResponse.getHeaders();
remaining = headers.get("X-RateLimit-Remaining");
reset = headers.get("X-RateLimit-Reset");

if (remaining != null & reset != null) {
    Instant resetInstant = Instant.ofEpochSecond(Long.parseLong(reset.get(0)));
    System.out.println("API calls remaining: " + remaining);
    System.out.println("Next Reset: " + resetInstant);
}
String envelopeId = envelopeResponse.getData().getEnvelopeId();
```

**`Node.js`**

```javascript title="Node.js"
let envelope = await envelopeApi.createEnvelope(args.accountId, {
  envelopeDefinition: {
    envelopeIdStamping: 'true',
    emailSubject: 'Please sign',
    status: 'Created',
    documents: [
      {
        documentBase64: Buffer.from(docBytes).toString('base64'),
        name: 'lorem',
        fileExtension: 'pdf',
        documentId: '2',
      },
    ],
    recipients: {
      signers: [
        {
          name: 'Multi Bulk Recipient::signer',
          email: 'multiBulkRecipients-signer@docusign.com',
          roleName: 'signer',
          routingOrder: '1',
          status: 'created',
          deliveryMethod: 'email',
          recipientId: '1',
          recipientType: 'signer',
          tabs: docusign.Tabs.constructFromObject({
            signHereTabs: [docusign.SignHere.constructFromObject({
              anchorString: '/sn1/',
              anchorYOffset: '10',
              anchorUnits: 'pixels',
              anchorXOffset: '20',
            })],
          })
        },
      ],
      carbonCopies: [
        {
          name: 'Multi Bulk Recipient::cc',
          email: 'multiBulkRecipients-cc@docusign.com',
          roleName: 'cc',
          routingOrder: '2',
          status: 'created',
          deliveryMethod: 'email',
          recipientId: '2',
          recipientType: 'cc',
        },
      ],
    },
  },
}, (error, data, response) => {
   const headers = response?.headers;

   const remaining = headers?.['x-ratelimit-remaining'];
   const reset = headers?.['x-ratelimit-reset'];

   if (remaining && reset) {
     const resetInstant = new Date(Number(reset) * 1000);

     console.log(`API calls remaining: ${remaining}`);
     console.log(`Next Reset: ${resetInstant.toISOString()}`);
   }
  });
```

**`PHP`**

```php title="PHP"
$envelope_definition = BulkSendEnvelopesService::makeEnvelope(
    $demoDocsPath,
    $docPDF
);
$envelope = $envelope_api->createEnvelopeWithHttpInfo(
    $args["account_id"],
    $envelope_definition
);

$remaining = $envelope[2]['X-RateLimit-Remaining'] ?? null;
$reset = $envelope[2]['X-RateLimit-Reset'] ?? null;

if ($remaining !== null && $reset !== null) {
    $resetInstant = (new DateTime())->setTimestamp((int)$reset);
    error_log("API calls remaining: $remaining");
    error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
}
$envelope_id = $envelope[0]["envelope_id"];
```

**`PowerShell`**

```text title="PowerShell"
$body = @"
{
	"documents": [{
		"documentBase64": "$(Get-Content $doc1Base64)",
		"documentId": "1",
		"fileExtension": "txt",
		"name": "NDA"
	}],
	"envelopeIdStamping": "true",
	"emailSubject": "Please sign",
	"recipients": {
		"signers": [{
			"name": "Multi Bulk Recipient::signer",
			"email": "multiBulkRecipients-signer@docusign.com",
			"roleName": "signer",
			"routingOrder": "1",
			"recipientId" : "1",
			"recipientType" : "signer",
			"delieveryMethod" : "Email",
			"status": "created",
			"tabs": {
				"signHereTabs": [{
					"documentId": "1",
					"name": "SignHereTab",
					"pageNumber": "1",
					"recipientId": "1",
					"tabLabel": "SignHereTab",
					"xPosition": "200",
					"yPosition": "160"
				}]}
			}],
		"carbonCopies": [{
			"name": "Multi Bulk Recipient::cc",
			"email": "multiBulkRecipients-cc@docusign.com",
			"roleName": "cc",
			"routingOrder": "2",
			"recipientId" : "2",
			"recipientType" : "cc",
			"delieveryMethod" : "Email",
			"status": "created"
			}]
		},
	"status": "created"
}
"@

$uri = "https://demo.docusign.net/restapi/v2.1/accounts/$APIAccountId/envelopes"
$response = $null
if ($continue -eq $true) {
	try {
		Write-Output @"

        Creating a Draft Envelope
"@
		$response = Invoke-RestMethod -uri $uri -method post -headers $headers -body $body
		$response | ConvertTo-Json
		#Obtain the envelopeId from the API response.
		$envelopeId = $response.envelopeId
	}
	catch {
		Write-Output "Envelope creation failed"
		#On failure, display a notification, X-DocuSign-TraceToken, error message, and the command that triggered the error
		$continue = $false
		$int = 0
		foreach ($header in $_.Exception.Response.Headers) {
			if ($header -eq "X-DocuSign-TraceToken") { Write-Output "TraceToken : " $_.Exception.Response.Headers[$int] }
			$int++
		}
		Write-Output "Error : "$_.ErrorDetails.Message
		Write-Output "Command : "$_.InvocationInfo.Line
	}
}
```

**`Python`**

```python title="Python"
envelope_api = EnvelopesApi(api_client)
envelope_definition = cls.make_draft_envelope(args["doc_pdf"])
(envelope, status, headers) = envelope_api.create_envelope_with_http_info(account_id=args["account_id"], envelope_definition=envelope_definition)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")

envelope_id = envelope.envelope_id
```

**`Ruby`**

```ruby title="Ruby"
envelope_api = create_envelope_api(args)
envelope_definition = make_envelope
envelope, _status, headers = envelope_api.create_envelope_with_http_info(args[:account_id], envelope_definition, DocuSign_eSign::CreateEnvelopeOptions.default)

remaining = headers['X-RateLimit-Remaining']
reset = headers['X-RateLimit-Reset']

if remaining && reset
  reset_date = Time.at(reset.to_i).utc
  puts "API calls remaining: #{remaining}"
  puts "Next Reset: #{reset_date}"
end

envelope_id = envelope.envelope_id
```

## Step 5. Attach your bulk list ID to the envelope

Attach the `listID` obtained in step 3 to your envelope as a custom field named `mailingListId`. Make a request to the eSignature REST API using the generated envelope ID from step 4.

**`Bash`**

```bash title="Bash"
printf \
'{
	"listCustomFields": [],
	"textCustomFields": [{
		"name": "mailingListId",
		"required": false,
		"show": false,
		"value": "'"$BULK_LIST_ID"'"
	}]
}	
' >> $request_data
echo ""
echo "Adding the listId as an envelope custom field."
echo ""
response=$(mktemp /tmp/response-bs.XXXXXX)
Status=$(curl -w '%{http_code}' -i --request POST ${base_path}/v2.1/accounts/${account_id}/envelopes/${ENVELOPE_ID}/custom_fields \
     "${Headers[@]}" \
     --data-binary @${request_data} \
     --output ${response})
```

**`C#`**

```csharp title="C#"
var fields = new CustomFields
{
    ListCustomFields = new List<ListCustomField> { },

    TextCustomFields = new List<TextCustomField>
        {
            new TextCustomField
            {
                Name = "mailingListId",
                Required = "false",
                Show = "false",
                Value = createBulkListResult.Data.ListId, // Adding the BULK_LIST_ID as an Envelope Custom Field
            },
        },
};
envelopesApi.CreateCustomFields(accountId, envelopeResults.EnvelopeId, fields);
```

**`Java`**

```java title="Java"
CustomFields customFields = createCustomFields(bulkListId);
var createCustomFields = envelopesApi.createCustomFieldsWithHttpInfo(accountId, envelopeId, customFields);
headers = createCustomFields.getHeaders();
remaining = headers.get("X-RateLimit-Remaining");
reset = headers.get("X-RateLimit-Reset");

if (remaining != null & reset != null) {
    Instant resetInstant = Instant.ofEpochSecond(Long.parseLong(reset.get(0)));
    System.out.println("API calls remaining: " + remaining);
    System.out.println("Next Reset: " + resetInstant);
}
```

**`Node.js`**

```javascript title="Node.js"
await envelopeApi.createCustomFields(args.accountId, envelope.data.envelopeId, {
  customFields: {
    textCustomFields: [
      {
        name: 'mailingListId',
        required: 'false',
        show: 'false',
        value: bulkList.data.listId,
      },
    ],
  },
}, (error, data, response) => {
   const headers = response?.headers;

   const remaining = headers?.['x-ratelimit-remaining'];
   const reset = headers?.['x-ratelimit-reset'];

   if (remaining && reset) {
     const resetInstant = new Date(Number(reset) * 1000);

     console.log(`API calls remaining: ${remaining}`);
     console.log(`Next Reset: ${resetInstant.toISOString()}`);
   }
  });
```

**`PHP`**

```php title="PHP"
$text_custom_fields = new TextCustomField(
    [
        "name" => "mailingListId",
        "required" => "false",
        "show" => "false",
        "value" => $bulk_list_id
    ]
);

$custom_fields = new CustomFields(
    [
        "list_custom_fields" => [],
        "text_custom_fields" => [$text_custom_fields]
    ]
);

$response = $envelope_api->createCustomFieldsWithHttpInfo($args["account_id"], $envelope_id, $custom_fields);
$remaining = $response[2]['X-RateLimit-Remaining'] ?? null;
$reset = $response[2]['X-RateLimit-Reset'] ?? null;

if ($remaining !== null && $reset !== null) {
    $resetInstant = (new DateTime())->setTimestamp((int)$reset);
    error_log("API calls remaining: $remaining");
    error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
}
```

**`PowerShell`**

```text title="PowerShell"
$body = @"
{
	"listCustomFields": [],
	"textCustomFields": [{
		"name": "mailingListId",
		"required": false,
		"show": false,
		"value": "${listId}"
	}]
}
"@

$uri = "https://demo.docusign.net/restapi/v2.1/accounts/$APIAccountId/envelopes/$envelopeId/custom_fields"
try {
	Write-Output @"

        Adding the ListId as an Envelope Custom Field
"@
	$response = Invoke-RestMethod -uri $uri -headers $headers -body $body -method POST
	$response | ConvertTo-Json
}
catch {
	Write-Output "Adding envelope custom field has failed"
	# On failure, display a notification, X-DocuSign-TraceToken, error message, and the command that triggered the error
	$continue = $false
	$int = 0
	foreach ($header in $_.Exception.Response.Headers) {
		if ($header -eq "X-DocuSign-TraceToken") { Write-Output "TraceToken : " $_.Exception.Response.Headers[$int] }
		$int++
	}
	Write-Output "Error : "$_.ErrorDetails.Message
	Write-Output "Command : "$_.InvocationInfo.Line
}
```

**`Python`**

```python title="Python"
text_custom_fields = TextCustomField(name="mailingListId", required="false", show="false", value=bulk_list_id)
custom_fields = CustomFields(list_custom_fields=[], text_custom_fields=[text_custom_fields])
(response, status, headers) = envelope_api.create_custom_fields_with_http_info(
    account_id=args["account_id"],
    envelope_id=envelope_id,
    custom_fields=custom_fields
)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")
```

**`Ruby`**

```ruby title="Ruby"
_results, _status, headers = envelope_api.create_custom_fields_with_http_info(args[:account_id], envelope_id, custom_fields(bulk_list_id))

remaining = headers['X-RateLimit-Remaining']
reset = headers['X-RateLimit-Reset']

if remaining && reset
  reset_date = Time.at(reset.to_i).utc
  puts "API calls remaining: #{remaining}"
  puts "Next Reset: #{reset_date}"
end
```

## Step 6. Initiate bulk send

Now that you have constructed an envelope and bulk list, make a request to the eSignature REST API and include the bulk list ID and envelope ID to initiate the bulk sending of envelopes to multiple recipients. Here’s how you make the call, but be sure to substitute your bulk list ID for \{BULK_LIST_ID} and envelope ID for \{ENVELOPE_ID}. Extract the `batchId` from the response into a variable for use in the next step:

**`Bash`**

```bash title="Bash"
printf \
'{
	"listId": "'"${BULK_LIST_ID}"'",
	"envelopeOrTemplateId": "'"${ENVELOPE_ID}"'",
}	
' >> $request_data

echo ""
echo "Initiating the Bulk Send."
echo ""
response=$(mktemp /tmp/response-bs.XXXXXX)
Status=$(curl -w '%{http_code}' -i --request POST ${base_path}/v2.1/accounts/${account_id}/bulk_send_lists/${BULK_LIST_ID}/send \
     "${Headers[@]}" \
     --data-binary @${request_data} \
     --output ${response})

#If the Status code returned is greater than 201 (OK / Accepted), display an error message along with the API response. 
if [[ "$Status" -gt "201" ]] ; then
    echo ""
	echo "Initiating the Bulk Send has failed"
    echo ""
	cat $response
	exit 0
fi

echo ""
echo "Response:"
cat $response
echo ""

batchId=`cat $response | grep batchId | sed 's/.*\"batchId\":\"//' | sed 's/\",.*//'`
```

**`C#`**

```csharp title="C#"
var bulkRequestResult = bulkEnvelopesApi.CreateBulkSendRequestWithHttpInfo(accountId, createBulkListResult.Data.ListId, new BulkSendRequest { EnvelopeOrTemplateId = envelopeResults.EnvelopeId });
bulkRequestResult.Headers.TryGetValue("X-RateLimit-Remaining", out remaining);
bulkRequestResult.Headers.TryGetValue("X-RateLimit-Reset", out reset);

if (reset != null && remaining != null)
{
    DateTime resetDate = DateTimeOffset.FromUnixTimeSeconds(long.Parse(reset)).UtcDateTime;
    Console.WriteLine("API calls remaining: " + remaining);
    Console.WriteLine("Next Reset: " + resetDate);
}
```

**`Java`**

```java title="Java"
BulkSendRequest request = new BulkSendRequest();
request.setEnvelopeOrTemplateId(envelopeId);

var bulkSendResponse = bulkEnvelopesApi.createBulkSendRequestWithHttpInfo(accountId, bulkListId, request);
headers = createCustomFields.getHeaders();
remaining = headers.get("X-RateLimit-Remaining");
reset = headers.get("X-RateLimit-Reset");

if (remaining != null & reset != null) {
    Instant resetInstant = Instant.ofEpochSecond(Long.parseLong(reset.get(0)));
    System.out.println("API calls remaining: " + remaining);
    System.out.println("Next Reset: " + resetInstant);
}
return bulkSendResponse.getData().getBatchId();
```

**`Node.js`**

```javascript title="Node.js"
let bulkResult = await bulkEnvelopesApi.createBulkSendRequest(
  args.accountId,
  bulkList.data.listId,
  {
    bulkSendRequest: {
      envelopeOrTemplateId: envelope.data.envelopeId,
    },
  }, (error, data, response) => {
   const headers = response?.headers;

   const remaining = headers?.['x-ratelimit-remaining'];
   const reset = headers?.['x-ratelimit-reset'];

   if (remaining && reset) {
     const resetInstant = new Date(Number(reset) * 1000);

     console.log(`API calls remaining: ${remaining}`);
     console.log(`Next Reset: ${resetInstant.toISOString()}`);
   }
  });
```

**`PHP`**

```php title="PHP"
$bulk_send_request = new BulkSendRequest(['envelope_or_template_id' => $envelope_id]);

$batch = $bulk_envelopes_api->createBulkSendRequestWithHttpInfo(
    $args["account_id"],
    $bulk_list_id,
    $bulk_send_request
);

$remaining = $batch[2]['X-RateLimit-Remaining'] ?? null;
$reset = $batch[2]['X-RateLimit-Reset'] ?? null;

if ($remaining !== null && $reset !== null) {
    $resetInstant = (new DateTime())->setTimestamp((int)$reset);
    error_log("API calls remaining: $remaining");
    error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
}
```

**`PowerShell`**

```text title="PowerShell"
$body = @"
{
	"listId": "${listId}",
	"envelopeOrTemplateId": "${envelopeId}",
}
"@

$uri = "https://demo.docusign.net/restapi/v2.1/accounts/$APIAccountId/bulk_send_lists/$listId/send"
if ($continue -eq $true) {
	try {
		Write-Output @"

    Initiating Bulk Send
"@
		$response = Invoke-RestMethod -uri $uri -headers $headers -body $body -method POST
		$bulkBatchId = $response.batchId
		$response | ConvertTo-Json
	}
	catch {
		Write-Output "Envelope send failed"
		# On failure, display a notification, X-DocuSign-TraceToken, error message, and the command that triggered the error
		$continue = $false
		$int = 0
		foreach ($header in $_.Exception.Response.Headers) {
			if ($header -eq "X-DocuSign-TraceToken") { Write-Output "TraceToken : " $_.Exception.Response.Headers[$int] }
			$int++
		}
		Write-Output "Error : "$_.ErrorDetails.Message
		Write-Output "Command : "$_.InvocationInfo.Line
	}
}
```

**`Python`**

```python title="Python"
bulk_send_request = BulkSendRequest(envelope_or_template_id=envelope_id)
(batch, status, headers) = bulk_envelopes_api.create_bulk_send_request_with_http_info(
    account_id=args["account_id"],
    bulk_send_list_id=bulk_list_id,
    bulk_send_request=bulk_send_request
)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")

batch_id = batch.batch_id
```

**`Ruby`**

```ruby title="Ruby"
bulk_send_request = DocuSign_eSign::BulkSendRequest.new(envelopeOrTemplateId: envelope_id)
batch, _status, headers = bulk_envelopes_api.create_bulk_send_request_with_http_info(args[:account_id], bulk_list_id, bulk_send_request)

remaining = headers['X-RateLimit-Remaining']
reset = headers['X-RateLimit-Reset']

if remaining && reset
  reset_date = Time.at(reset.to_i).utc
  puts "API calls remaining: #{remaining}"
  puts "Next Reset: #{reset_date}"
end

batch_id = batch.batch_id
```

## Step 7. Confirm successful batch send

Now that your bulk send operation has been initiated, check the progress of the operation by sending a request to the eSignature REST API, furnishing the `batchId` that was generated during the previous step. The sample code as configured here will wait for 10 seconds to give the API enough time to process the bulk envelopes from step 6.

> **Note**
>
> View the source code for this how-to on GitHub: [Bash](https://github.com/docusign/code-examples-bash/blob/master/examples/eSignature/eg031BulkSending.sh), [C#](https://github.com/docusign/code-examples-csharp/blob/master/launcher-csharp/eSignature/Examples/SendBulkEnvelopes.cs), [Java](https://github.com/docusign/code-examples-java/blob/master/src/main/java/com/docusign/controller/eSignature/examples/EG031ControllerBulkSendEnvelopes.java), [Node.js](https://github.com/docusign/code-examples-node/blob/master/lib/eSignature/examples/bulkSendEnvelope.js), [PHP](https://github.com/docusign/code-examples-php/blob/master/src/Services/Examples/eSignature/BulkSendEnvelopesService.php), [PowerShell](https://github.com/docusign/code-examples-powershell/blob/master/examples/eSignature/eg031BulkSending.ps1), [Python](https://github.com/docusign/code-examples-python/blob/master/app/eSignature/examples/eg031_bulk_send.py), or [Ruby](https://github.com/docusign/code-examples-ruby/blob/main/app/services/e_sign/eg031_bulk_sending_envelopes_service.rb).

**`Bash`**

```bash title="Bash"
echo ""
echo "Confirming Bulk Send has initiated. -- ${batchId}"
echo ""

sleep 10s

response=$(mktemp /tmp/response-bs.XXXXXX)
Status=$(curl -w '%{http_code}' -i --request GET ${base_path}/v2.1/accounts/${account_id}/bulk_send_batch/${batchId} \
     "${Headers[@]}" \
     --output ${response})
```

**`C#`**

```csharp title="C#"
var getBulkSendBatchStatus = bulkEnvelopesApi.GetBulkSendBatchStatusWithHttpInfo(accountId, bulkRequestResult.Data.BatchId);
bulkRequestResult.Headers.TryGetValue("X-RateLimit-Remaining", out remaining);
bulkRequestResult.Headers.TryGetValue("X-RateLimit-Reset", out reset);

if (reset != null && remaining != null)
{
    DateTime resetDate = DateTimeOffset.FromUnixTimeSeconds(long.Parse(reset)).UtcDateTime;
    Console.WriteLine("API calls remaining: " + remaining);
    Console.WriteLine("Next Reset: " + resetDate);
}

return getBulkSendBatchStatus.Data;
```

**`Java`**

```java title="Java"
public static BulkSendBatchStatus getBulkSendBatchStatus(
        BulkEnvelopesApi bulkEnvelopesApi,
        String accountId,
        String batchId,
        long bulkRequestDelay) throws ApiException, InterruptedException {
    TimeUnit.SECONDS.sleep(bulkRequestDelay);
    // For 2000 recipients, it can take about an hour
    var getBulkSendBatchStatus = bulkEnvelopesApi.getBulkSendBatchStatusWithHttpInfo(accountId, batchId);
    Map<String, List<String>> headers = getBulkSendBatchStatus.getHeaders();
    java.util.List<String> remaining = headers.get("X-RateLimit-Remaining");
    List<String> reset = headers.get("X-RateLimit-Reset");

    if (remaining != null & reset != null) {
        Instant resetInstant = Instant.ofEpochSecond(Long.parseLong(reset.get(0)));
        System.out.println("API calls remaining: " + remaining);
        System.out.println("Next Reset: " + resetInstant);
    }
    return getBulkSendBatchStatus.getData();
}
```

**`Node.js`**

```javascript title="Node.js"
let results = await bulkEnvelopesApi.getBulkSendBatchStatus(
  args.accountId,
  bulkResult.data.batchId, (error, data, response) => {
   const headers = response?.headers;

   const remaining = headers?.['x-ratelimit-remaining'];
   const reset = headers?.['x-ratelimit-reset'];

   if (remaining && reset) {
     const resetInstant = new Date(Number(reset) * 1000);

     console.log(`API calls remaining: ${remaining}`);
     console.log(`Next Reset: ${resetInstant.toISOString()}`);
   }
  });
```

**`PHP`**

```php title="PHP"
try {
    $bulkSendBatchStatus = $bulk_envelopes_api->getBulkSendBatchStatusWithHttpInfo(
        $args['account_id'],
        $batch[0]['batch_id']
    );

    $remaining = $bulkSendBatchStatus[2]['X-RateLimit-Remaining'] ?? null;
    $reset = $bulkSendBatchStatus[2]['X-RateLimit-Reset'] ?? null;

    if ($remaining !== null && $reset !== null) {
        $resetInstant = (new DateTime())->setTimestamp((int)$reset);
        error_log("API calls remaining: $remaining");
        error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
    }
} catch (ApiException $e) {
    $clientService->showErrorTemplate($e);
    exit;
}

return $bulkSendBatchStatus[0];
```

**`PowerShell`**

```text title="PowerShell"
$uri = "https://demo.docusign.net/restapi/v2.1/accounts/$APIAccountId/bulk_send_batch/$bulkBatchId"
if ($continue -eq $true) {
	$response = $null
	try {
		Write-Output @"

        Retrieving Bulk Send status
"@
		Start-Sleep -Second 10
		$response = Invoke-RestMethod -uri $uri -headers $headers -method GET
		$response | ConvertTo-Json
	}
	catch {
		Write-Output "Unable to retrieve Bulk Status."
		# On failure, display a notification, X-DocuSign-TraceToken, error message, and the command that triggered the error
		foreach ($header in $_.Exception.Response.Headers) {
			if ($header -eq "X-DocuSign-TraceToken") { Write-Output "TraceToken : " $_.Exception.Response.Headers[$int] }
			$int++
		}
		Write-Output "Error : "$_.ErrorDetails.Message
		Write-Output "Command : "$_.InvocationInfo.Line
	}
}
```

**`Python`**

```python title="Python"
(response, status, headers) = bulk_envelopes_api.get_bulk_send_batch_status_with_http_info(
    account_id=args["account_id"],
    bulk_send_batch_id=batch_id
)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")
```

**`Ruby`**

```ruby title="Ruby"
results, _status, headers = bulk_envelopes_api.get_bulk_send_batch_status_with_http_info(args[:account_id], batch_id)

remaining = headers['X-RateLimit-Remaining']
reset = headers['X-RateLimit-Reset']

if remaining && reset
  reset_date = Time.at(reset.to_i).utc
  puts "API calls remaining: #{remaining}"
  puts "Next Reset: #{reset_date}"
end
```

## Expected response

If you run this code example from our Quickstart or launcher projects, you should see a JSON response similar to the code shown.

**`Sample response`**

```json title="Sample response"
{
  "batchId": "fefcbf2c-xxxx-xxxx-xxxx-681da196098b",
  "batchSize": "2",
  "sent": "0",
  "queued": "2",
  "failed": "0",
  "envelopeIdOrTemplateId": "caede1c7-xxxx-xxxx-xxxx-4657e05ae0db",
  "mailingListId": "3b464b42-xxxx-xxxx-xxxx-d8613e70030f",
  "ownerUserId": "c316f86c-xxxx-xxxx-xxxx-6b39358381d2",
  "senderUserId": "c316f86c-xxxx-xxxx-xxxx-6b39358381d2",
  "batchName": "sample.csv",
  "submittedDate": "2021-12-30T07:07:28.2900000Z",
  "envelopesUri": "/bulk_send_batch/fefcbf2c-xxxx-xxxx-xxxx-681da196098b/envelopes?start_position=0&count=40",
  "mailingListName": "sample.csv",
  "envelopesInfo": {
    "completed": "0",
    "declined": "0",
    "voided": "0"
  },
  "bulkErrors": []
}
```

## Next steps

* Run this code example from our [Quickstart](/docs/esign-rest-api/quickstart) or launcher projects:    [Bash](https://github.com/docusign/code-examples-bash), [C#](https://github.com/docusign/code-examples-csharp), [Java](https://github.com/docusign/code-examples-java), [Node.js](https://github.com/docusign/code-examples-node), [PHP](https://github.com/docusign/code-examples-php), [PowerShell](https://github.com/docusign/code-examples-powershell), [Python](https://github.com/docusign/code-examples-python), or [Ruby](https://github.com/docusign/code-examples-ruby).
* Learn more about [Bulk sending envelopes](/docs/esign-rest-api/esign101/concepts/envelopes/bulk-send).
* See how to [Go-Live](/docs/esign-rest-api/go-live) with a completed app and find the base path for API calls in the production environment.