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
Copy link
Copy Markdown
Member

@michaelmwu michaelmwu commented Mar 21, 2026

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.

Copilot AI review requested due to automatic review settings March 21, 2026 10:02
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 21, 2026

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 name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The 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 ✅ Passed Docstring 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.

Copy link
Copy Markdown

Copilot AI left a comment

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
File Description
uv.lock Adds requests to the lockfile to support the DocuSeal client.
packages/shared/pyproject.toml Declares requests dependency for the shared package.
packages/shared/src/five08/settings.py Adds shared DocuSeal configuration fields + template-id normalization validator.
packages/shared/src/five08/clients/docuseal.py New shared DocuSeal client + helper for creating member agreement submissions.
packages/shared/src/five08/clients/__init__.py Exposes the new docuseal client module in the shared clients package.
apps/worker/src/five08/worker/config.py Removes worker-local DocuSeal template-id normalization (now handled in shared settings).
apps/discord_bot/src/five08/discord_bot/cogs/crm.py Adds the /send-member-agreement command and supporting helpers.
apps/discord_bot/README.md Documents the new slash command and its prerequisites/guardrails.
.env.example Documents new DOCUSEAL_BASE_URL and DOCUSEAL_API_KEY env vars.
tests/unit/test_shared_settings.py Adds test ensuring template ID env-like numeric strings coerce to int.
tests/unit/test_docuseal_client.py Adds unit tests for DocuSeal submission payload + API error handling.
tests/unit/test_crm.py Adds 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 thread packages/shared/src/five08/settings.py Outdated
Comment thread packages/shared/src/five08/clients/docuseal.py Outdated
Comment thread apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Copilot AI review requested due to automatic review settings March 21, 2026 10:45
@michaelmwu michaelmwu merged commit 31b4de1 into main Mar 21, 2026
6 checks passed
@michaelmwu michaelmwu deleted the michaelmwu/docuseal-agreement branch March 21, 2026 10:46
Copy link
Copy Markdown

Copilot AI left a comment

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=

Copy link

Copilot AI Mar 21, 2026

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}"
Copy link

Copilot AI Mar 21, 2026

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"
raise DocusealAPIError(
f"Wrong request, status code is {response.status_code}, reason is {message}"
body_preview = " ".join((response.text or "").strip().split())
if len(body_preview) > 200:
body_preview = body_preview[:200] + "..."
if not body_preview:
body_preview = "<empty>"
raise DocusealAPIError(
f"Wrong request, status code is {response.status_code}, body preview: {body_preview}"

Copilot uses AI. Check for mistakes.
Sign up for free to 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