Skip to content

feat: add DocuSeal member agreement send command - #209

Merged
michaelmwu merged 3 commits into
mainfrom
michaelmwu/docuseal-agreement
Mar 21, 2026
Merged

feat: add DocuSeal member agreement send command#209
michaelmwu merged 3 commits into
mainfrom
michaelmwu/docuseal-agreement

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Mar 21, 2026

Copy link
Copy Markdown
Member

Description

  • add a shared DocuSeal client plus shared settings for the base URL, API key, and member agreement template id
  • add a /send-member-agreement Discord CRM command that uses the usual contact search flow, includes Discord username lookup, sends the default DocuSeal email, and warns when the agreement is already signed
  • document the new env vars and update the shared dependency lockfile

Related Issue

  • None

How Has This Been Tested?

  • uv run mypy apps/api/src/five08/ apps/discord_bot/src/five08/ apps/worker/src/five08/ packages/shared/src/five08/ --ignore-missing-imports --explicit-package-bases
  • uv run pytest tests/unit/test_docuseal_client.py tests/unit/test_shared_settings.py tests/unit/test_crm.py -k 'send_member_agreement or docuseal or shared_settings_docuseal_template'

Summary by CodeRabbit

  • New Features

    • Added /send-member-agreement Discord command (Steering Committee role required) to send member agreements for signature via DocuSeal.
    • Integrated DocuSeal client for managing member agreement submissions.
  • Documentation

    • Added documentation for the /send-member-agreement command, including usage and requirements.
  • Chores

    • Added DocuSeal configuration environment variables.

CopilotAI review requested due to automatic review settings March 21, 2026 10:02
@coderabbitai

coderabbitaiBot commented Mar 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e9118fd-375b-49a3-a887-857418a9df50

📥 Commits

Reviewing files that changed from the base of the PR and between 494eefc and ad817dc.

📒 Files selected for processing (3)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • apps/worker/src/five08/worker/config.py
  • tests/unit/test_crm.py
📝 Walkthrough

Walkthrough

This PR introduces a new /send-member-agreement Discord slash command that integrates with DocuSeal to send member agreement forms for signature. It centralizes DocuSeal configuration into SharedSettings, creates a reusable DocuSeal HTTP client, and removes redundant configuration from WorkerSettings.

Changes

Cohort / File(s)Summary
Configuration
.env.example, packages/shared/src/five08/settings.py, apps/worker/src/five08/worker/config.py
Added DocuSeal environment variables (DOCUSEAL_BASE_URL, DOCUSEAL_API_KEY) to .env.example and SharedSettings with validation; removed docuseal_member_agreement_template_id from WorkerSettings as configuration is now centralized in SharedSettings.
DocuSeal Client
packages/shared/src/five08/clients/docuseal.py, packages/shared/src/five08/clients/__init__.py, packages/shared/pyproject.toml
Implemented new DocusealClient class with HTTP request handling, JSON serialization, and auth headers; exposed via five08.clients module exports; added requests library dependency.
Discord Command
apps/discord_bot/src/five08/discord_bot/cogs/crm.py, apps/discord_bot/README.md
Added /send-member-agreement command (Steering Committee role required) with contact search, email selection logic, signature status checking, and DocuSeal submission creation; includes helpers for contact field normalization and validation.
Tests
tests/unit/test_crm.py, tests/unit/test_docuseal_client.py, tests/unit/test_shared_settings.py
Added unit tests covering successful submission flow, denial cases (already signed, missing email), contact search behavior, HTTP interaction isolation, and DocuSeal configuration validation.

Sequence Diagram

sequenceDiagram
actor User as Discord User
participant Discord as Discord Bot
participant CRM as EspoAPI/CRM
participant DS as DocuSeal API
User->>Discord: /send-member-agreement [search_term]
Discord->>CRM: Search contacts by search_term<br/>(including Discord username)
CRM-->>Discord: Contact(s) found
alt Single match found
Discord->>Discord: Check cMemberAgreementSignedAt
alt Not yet signed
Discord->>Discord: Extract preferred email<br/>(emailAddress or c508Email)
alt Email available
Discord->>DS: POST /submissions<br/>(template_id, email, name)
DS-->>Discord: {id, ...}
Discord-->>User: ✅ Submission created<br/>(with submission_id)
else Missing email
Discord-->>User: ❌ Contact has no email
end
else Already signed
Discord-->>User: ⚠️ Agreement already signed
end
else Multiple or no matches
Discord-->>User: Multiple contacts match,<br/>please choose one (up to 5)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A rabbit hops with glee so bright,
DocuSeals now flow just right!
Members sign with ease and grace,
Discord bots keep pace, keeping pace!
Config shared, no duplication,
Tests ensure smooth operation! 📝✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: add DocuSeal member agreement send command' accurately summarizes the main change: adding a new Discord command to send member agreements via DocuSeal integration.
Docstring Coverage✅ PassedDocstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/docuseal-agreement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds DocuSeal integration to the shared package and exposes a new Discord CRM slash command to send a member agreement submission to a selected CRM contact, backed by new shared settings/env vars and unit tests.

Changes:

  • Introduce a shared DocuSeal client helper and shared settings for DocuSeal base URL, API key, and member agreement template ID.
  • Add /send-member-agreement Discord CRM command (with Discord username search support) and auditing/guardrails for “already signed” and “missing email”.
  • Update documentation, env examples, and dependency lockfiles; add unit tests for settings, client, and command behavior.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
uv.lockAdds requests to the lockfile to support the DocuSeal client.
packages/shared/pyproject.tomlDeclares requests dependency for the shared package.
packages/shared/src/five08/settings.pyAdds shared DocuSeal configuration fields + template-id normalization validator.
packages/shared/src/five08/clients/docuseal.pyNew shared DocuSeal client + helper for creating member agreement submissions.
packages/shared/src/five08/clients/__init__.pyExposes the new docuseal client module in the shared clients package.
apps/worker/src/five08/worker/config.pyRemoves worker-local DocuSeal template-id normalization (now handled in shared settings).
apps/discord_bot/src/five08/discord_bot/cogs/crm.pyAdds the /send-member-agreement command and supporting helpers.
apps/discord_bot/README.mdDocuments the new slash command and its prerequisites/guardrails.
.env.exampleDocuments new DOCUSEAL_BASE_URL and DOCUSEAL_API_KEY env vars.
tests/unit/test_shared_settings.pyAdds test ensuring template ID env-like numeric strings coerce to int.
tests/unit/test_docuseal_client.pyAdds unit tests for DocuSeal submission payload + API error handling.
tests/unit/test_crm.pyAdds unit tests covering the new Discord command behavior and search options.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/shared/src/five08/settings.py Outdated
Comment threadpackages/shared/src/five08/clients/docuseal.py Outdated
Comment threadapps/discord_bot/src/five08/discord_bot/cogs/crm.py
CopilotAI review requested due to automatic review settings March 21, 2026 10:45
@michaelmwu
michaelmwu merged commit 31b4de1 into mainMar 21, 2026
6 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/docuseal-agreement branch March 21, 2026 10:46

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread.env.example
Comment on lines 96 to 102
# Optional: restrict Docuseal agreements to this template id.
# If unset, Docuseal agreement processing is ignored.
DOCUSEAL_MEMBER_AGREEMENT_TEMPLATE_ID=
# Required for sending member agreement requests from Discord.
DOCUSEAL_BASE_URL=
DOCUSEAL_API_KEY=

CopilotAIMar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new env var comment says only DOCUSEAL_BASE_URL and DOCUSEAL_API_KEY are required for sending member agreements, but /send-member-agreement also hard-requires DOCUSEAL_MEMBER_AGREEMENT_TEMPLATE_ID (and README lists it as a prerequisite). Update this section so the example env file doesn’t imply the template id is optional for the Discord send flow.

Suggested change
# Optional: restrict Docuseal agreements to this template id.
# If unset, Docuseal agreement processing is ignored.
DOCUSEAL_MEMBER_AGREEMENT_TEMPLATE_ID=
# Required for sending member agreement requests from Discord.
DOCUSEAL_BASE_URL=
DOCUSEAL_API_KEY=
# Docuseal member agreements (required for /send-member-agreement Discord command).
# All three variables below are required for sending member agreement requests from Discord.
DOCUSEAL_MEMBER_AGREEMENT_TEMPLATE_ID=
DOCUSEAL_BASE_URL=
DOCUSEAL_API_KEY=

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +54
message = response.text.strip() or "Unknown Error"
raise DocusealAPIError(
f"Wrong request, status code is {response.status_code}, reason is {message}"

CopilotAIMar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DocusealClient.request() includes the full response.text in the raised DocusealAPIError for non-2xx responses. This text can be very large or contain unexpected markup, which can bloat logs/audit records and complicate downstream sanitization. Consider normalizing whitespace and truncating the body to a bounded preview (similar to the invalid-JSON path) before embedding it in the exception message.

Suggested change
message=response.text.strip() or"Unknown Error"
raiseDocusealAPIError(
f"Wrong request, status code is {response.status_code}, reason is {message}"
body_preview=" ".join((response.textor"").strip().split())
iflen(body_preview) >200:
body_preview=body_preview[:200] +"..."
ifnotbody_preview:
body_preview="<empty>"
raiseDocusealAPIError(
f"Wrong request, status code is {response.status_code}, body preview: {body_preview}"

Copilot uses AI. Check for mistakes.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu