Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c669be4
update packages
RomanBachaloSigmaSoftware May 30, 2025
9a66c89
add second example
RomanBachaloSigmaSoftware Sep 10, 2025
b677f9f
Merge pull request #169 from docusign/maintenance/may-2025
InbarGazit Sep 10, 2025
a341c43
Updating few DocuSign to Docusign
InbarGazit Sep 10, 2025
f207ccd
Merge branch 'master' into feature/webforms-example-2
RomanBachaloSigmaSoftware Sep 11, 2025
dfbc54c
Merge branch 'feature/webforms-example-2' of https://github.com/docus…
RomanBachaloSigmaSoftware Sep 11, 2025
aeb95a2
Merge pull request #172 from docusign/feature/webforms-example-2
InbarGazit Sep 11, 2025
62f10e7
DEVDOCS-17408 - changing expiration time to 24 hours
InbarGazit Sep 15, 2025
e852e48
Merge pull request #174 from docusign/DEVDOCS-17408
InbarGazit Sep 16, 2025
de3953e
add example
RomanBachaloSigmaSoftware Sep 23, 2025
df90d6e
support custom folders
RomanBachaloSigmaSoftware Oct 6, 2025
1f7c926
Merge pull request #175 from docusign/feature/delete-restore-envelope
balaji1103 Oct 9, 2025
076a7d8
separate move_envelope methods
RomanBachaloSigmaSoftware Oct 16, 2025
0372bee
Merge branch 'master' into feature/delete-restore-envelope
RomanBachaloSigmaSoftware Oct 16, 2025
9efdc1e
update property name
RomanBachaloSigmaSoftware Oct 16, 2025
8e73910
Merge pull request #176 from docusign/feature/delete-restore-envelope
balaji1103 Oct 19, 2025
d18736c
update examples
RomanBachaloSigmaSoftware Jan 2, 2026
a8638e3
move file name to consts
RomanBachaloSigmaSoftware Jan 2, 2026
906af9a
remove doc_file from args
RomanBachaloSigmaSoftware Jan 2, 2026
612139e
fix eSign example 7
RomanBachaloSigmaSoftware Jan 2, 2026
72374da
codeql fix
RomanBachaloSigmaSoftware Jan 2, 2026
049b319
Merge pull request #177 from docusign/feature/add-http-info
balaji1103 Jan 7, 2026
d0e4889
Send envelope with multiple delivery channels (#179)
RomanBachaloSigmaSoftware Jan 26, 2026
ed821b0
merge updates
karissarjacobsen Jan 27, 2026
037afa1
merge updates
karissarjacobsen Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
app.register_blueprint(esignature_views.eg043)
app.register_blueprint(esignature_views.eg044)
app.register_blueprint(esignature_views.eg045)
app.register_blueprint(esignature_views.eg046)

app.register_blueprint(connect_views.cneg001)

Expand Down
253 changes: 253 additions & 0 deletions app/eSignature/examples/eg046_multiple_delivery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import base64
from datetime import datetime as dt, timezone
from os import path

from docusign_esign import (
EnvelopesApi,
EnvelopeDefinition,
Document,
Signer,
CarbonCopy,
SignHere,
Tabs,
Recipients,
RecipientPhoneNumber,
RecipientAdditionalNotification
)

from flask import session, request

from ...consts import demo_docs_path, pattern
from ...docusign import create_api_client
from ...ds_config import DS_CONFIG


class Eg046MultipleDeliveryController:
@staticmethod
def get_args():
"""Get request and session arguments"""

# More data validation would be a good idea here
# Strip anything other than characters listed
signer_name = pattern.sub("", request.form.get("signer_name"))
signer_email = pattern.sub("", request.form.get("signer_email"))
cc_name = pattern.sub("", request.form.get("cc_name"))
cc_email = pattern.sub("", request.form.get("cc_email"))
signer_phone_number = request.form.get("signer_phone_number")
signer_country_code = request.form.get("signer_country_code")
cc_phone_number = request.form.get("cc_phone_number")
cc_country_code = request.form.get("cc_country_code")
delivery_method = request.form["delivery_method"]
envelope_args = {
"signer_name": signer_name,
"signer_email": signer_email,
"status": "sent",
"cc_name": cc_name,
"cc_email": cc_email,
"signer_country_code": signer_country_code,
"signer_phone_number": signer_phone_number,
"cc_country_code" :cc_country_code,
"cc_phone_number": cc_phone_number,
"delivery_method": delivery_method
}
args = {
"account_id": session["ds_account_id"],
"base_path": session["ds_base_path"],
"access_token": session["ds_access_token"],
"envelope_args": envelope_args
}
return args

@classmethod
def worker(cls, args):
"""
1. Create the envelope request object
2. Send the envelope
"""

#ds-snippet-start:eSign46Step3
envelope_args = args["envelope_args"]
# Create the envelope request object
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
envelope_definition = cls.make_envelope(envelope_args)
# Call Envelopes::create API method
# Exceptions will be caught by the calling function
envelopes_api = EnvelopesApi(api_client)
(results, status, headers) = envelopes_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 = results.envelope_id

return {"envelope_id": envelope_id}
#ds-snippet-end:eSign46Step3

#ds-snippet-start:eSign46Step2
@classmethod
def make_envelope(cls, args):
"""
Creates envelope:
document 1 (HTML) has signHere anchor tag: **signature_1**
document 2 (DOCX) has signHere anchor tag: /sn1/
document 3 (PDF) has signHere anchor tag: /sn1/
DocuSign will convert all of the documents to the PDF format.
The recipient’s field tags are placed using anchor strings.
The envelope has two recipients:
recipient 1: signer
recipient 2: cc
The envelope will be sent first to the signer via SMS.
After it is signed, a copy is sent to the cc recipient via SMS.
"""
# Create the envelope definition
env = EnvelopeDefinition(
email_subject="Please sign this document set"
)
doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")).decode("ascii")
# Read files 2 and 3 from a local folder
# The reads could raise an exception if the file is not available!
with open(path.join(demo_docs_path, DS_CONFIG["doc_docx"]), "rb") as file:
doc2_docx_bytes = file.read()
doc2_b64 = base64.b64encode(doc2_docx_bytes).decode("ascii")
with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file:
doc3_pdf_bytes = file.read()
doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode("ascii")

# Create the document models
document1 = Document( # Create the DocuSign document object
document_base64=doc1_b64,
name="Order acknowledgement", # Can be different from actual file name
file_extension="html", # Many different document types are accepted
document_id="1" # A label used to reference the doc
)
document2 = Document( # Create the DocuSign document object
document_base64=doc2_b64,
name="Battle Plan", # Can be different from actual file name
file_extension="docx", # Many different document types are accepted
document_id="2" # A label used to reference the doc
)
document3 = Document( # Create the DocuSign document object
document_base64=doc3_b64,
name="Lorem Ipsum", # Can be different from actual file name
file_extension="pdf", # Many different document types are accepted
document_id="3" # A label used to reference the doc
)
# The order in the docs array determines the order in the envelope
env.documents = [document1, document2, document3]

signer_phone_number = RecipientPhoneNumber(
country_code=args["signer_country_code"],
number=args["signer_phone_number"]
)
signer_additional_notification = RecipientAdditionalNotification(
secondary_delivery_method=args["delivery_method"],
phone_number=signer_phone_number
)

# Create the signer recipient model
signer1 = Signer(
name=args["signer_name"],
email=args["signer_email"],
recipient_id="1",
routing_order="1",
delivery_method="Email",
additional_notifications=[signer_additional_notification]
)

# Create a RecipientPhoneNumber and add it to the additional SMS notification
cc_phone_number = RecipientPhoneNumber(
country_code=args["cc_country_code"],
number=args["cc_phone_number"]
)

cc_additional_notification = RecipientAdditionalNotification(
secondary_delivery_method=args["delivery_method"],
phone_number=cc_phone_number
)

# Create a cc recipient to receive a copy of the documents
cc1 = CarbonCopy(
name=args["cc_name"],
email=args["cc_email"],
recipient_id="2",
routing_order="2",
delivery_method="Email",
additional_notifications=[cc_additional_notification]
)

# routingOrder (lower means earlier) determines the order of deliveries
# to the recipients. Parallel routing order is supported by using the
# same integer as the order for two or more recipients

# Create signHere fields (also known as tabs) on the documents
# We're using anchor (autoPlace) positioning
#
# The DocuSign platform searches throughout your envelope"s
# documents for matching anchor strings. So the
# signHere2 tab will be used in both document 2 and 3 since they
# use the same anchor string for their "signer 1" tabs
sign_here1 = SignHere(
anchor_string="**signature_1**",
anchor_units="pixels",
anchor_y_offset="10",
anchor_x_offset="20"
)

sign_here2 = SignHere(
anchor_string="/sn1/",
anchor_units="pixels",
anchor_y_offset="10",
anchor_x_offset="20"
)

# Add the tabs model (including the SignHere tabs) to the signer
# The Tabs object wants arrays of the different field/tab types
signer1.tabs = Tabs(sign_here_tabs=[sign_here1, sign_here2])

# Add the recipients to the envelope object
recipients = Recipients(signers=[signer1], carbon_copies=[cc1])
env.recipients = recipients

# Request that the envelope be sent by setting status to "sent"
# To request that the envelope be created as a draft, set to "created"
env.status = args["status"]

return env

@classmethod
def create_document1(cls, args):
""" Creates document 1 -- an html document"""

return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body style="font-family:sans-serif;margin-left:2em;">
<h1 style="font-family: "Trebuchet MS", Helvetica, sans-serif;
color: darkblue;margin-bottom: 0;">World Wide Corp</h1>
<h2 style="font-family: "Trebuchet MS", Helvetica, sans-serif;
margin-top: 0px;margin-bottom: 3.5em;font-size: 1em;
color: darkblue;">Order Processing Division</h2>
<h4>Ordered by {args["signer_name"]}</h4>
<p style="margin-top:0em; margin-bottom:0em;">Phone Number: {args["signer_phone_number"]}</p>
<p style="margin-top:0em; margin-bottom:0em;">Copy to: {args["cc_name"]}</p>
<p style="margin-top:3em;">
Candy bonbon pastry jujubes lollipop wafer biscuit biscuit. Topping brownie sesame snaps sweet roll pie.
Croissant danish biscuit soufflé caramels jujubes jelly. Dragée danish caramels lemon drops dragée.
Gummi bears cupcake biscuit tiramisu sugar plum pastry. Dragée gummies applicake pudding liquorice.
Donut jujubes oat cake jelly-o.
Dessert bear claw chocolate cake gummies lollipop sugar plum ice cream gummies cheesecake.
</p>
<!-- Note the anchor tag for the signature field is in white -->
<h3 style="margin-top:3em;">Agreed: <span style="color:white;">**signature_1**/</span></h3>
</body>
</html>
"""
#ds-snippet-end:eSign46Step2
1 change: 1 addition & 0 deletions app/eSignature/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@
from .eg043_shared_access import eg043
from .eg044_focused_view import eg044
from .eg045_delete_restore_envelope import eg045
from .eg046_multiple_delivery import eg046
92 changes: 92 additions & 0 deletions app/eSignature/views/eg046_multiple_delivery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
""" Example 046: Request a signature bt multiple delivery channels """

import json
from os import path

from docusign_esign.client.api_exception import ApiException
from flask import redirect, render_template, session, Blueprint, url_for

from ..examples.eg046_multiple_delivery import Eg046MultipleDeliveryController
from ...docusign import authenticate, ensure_manifest, get_example_by_number
from ...docusign.utils import is_cfr
from ...ds_config import DS_CONFIG
from ...error_handlers import process_error
from ...consts import API_TYPE

example_number = 46
api = API_TYPE["ESIGNATURE"]
eg = f"eg0{example_number}" # reference (and url) for this example
eg046 = Blueprint(eg, __name__)


@eg046.route(f"/{eg}", methods=["POST"])
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
@authenticate(eg=eg, api=api)
def send_by_multiple_channels():
"""
1. Get required arguments
2. Call the worker method
3. Render success response with envelopeId
"""
example = get_example_by_number(session["manifest"], example_number, api)

# 1. Get required arguments
args = Eg046MultipleDeliveryController.get_args()
try:
# 1. Call the worker method
results = Eg046MultipleDeliveryController.worker(args)
except ApiException as err:
error_body_json = err and hasattr(err, "body") and err.body
# we can pull the DocuSign error code and message from the response body
try:
error_body = json.loads(error_body_json)
except json.decoder.JSONDecodeError:
error_body = {}
error_code = error_body and "errorCode" in error_body and error_body["errorCode"]

# check for specific error
if "ACCOUNT_LACKS_PERMISSIONS" in error_code:
error_message = example["CustomErrorTexts"][0]["ErrorMessage"]
return render_template(
"error.html",
error_code=error_code,
error_message=error_message
)

return process_error(err)

session["envelope_id"] = results["envelope_id"] # Save for use by other examples which need an envelopeId

# 2. Render success response with envelopeId
return render_template(
"example_done.html",
title=example["ExampleName"],
message=f"The envelope has been created and sent!<br/>Envelope ID {results['envelope_id']}."
)


@eg046.route(f"/{eg}", methods=["GET"])
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
@authenticate(eg=eg, api=api)
def get_view():
"""responds with the form for the example"""
example = get_example_by_number(session["manifest"], example_number, api)

cfr_status = is_cfr(session["ds_access_token"], session["ds_account_id"], session["ds_base_path"])
if cfr_status == "enabled":
if DS_CONFIG["quickstart"] == "true":
return redirect(url_for("eg041.get_view"))
else:
return render_template("cfr_error.html", title="Error")

return render_template(
"eSignature/eg046_multiple_delivery.html",
title=example["ExampleName"],
example=example,
source_file= "eg046_multiple_delivery.py",
source_url=DS_CONFIG["github_example_url"] + "eg046_multiple_delivery.py",
documentation=DS_CONFIG["documentation"] + eg,
show_doc=DS_CONFIG["documentation"],
signer_name=DS_CONFIG["signer_name"],
signer_email=DS_CONFIG["signer_email"]
)
Loading