[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build an E-Signature System

Build an E-Signature System

Build a document signing system with PDF field placement, signing workflows, audit trails, multi-party signing order, reminders, and legal compliance — replacing DocuSign for internal workflows.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 6 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
>

s3-storage

v1.0.0

Manages S3-compatible object storage (AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, Wasabi, Supabase Storage). Use when the user wants to create buckets, upload/download files, set up lifecycle policies, configure CORS, manage presigned URLs, implement multipart uploads, set up replication, handle versioning, configure access policies, or build file management features on top of S3-compatible APIs. Trigger words: s3, minio, r2, object storage, bucket, presigned url, multipart upload, lifecycle policy, s3 cors, storage backend, file storage, blob storage, spaces, backblaze, wasabi.

93/100 quality
1.96× impact
SAFE
View skill
$

The Problem

Oscar leads ops at a 40-person company. They send 200+ contracts monthly using DocuSign at $25/user/month ($6K/year). Contracts follow a rigid flow: sales rep fills details → legal reviews → client signs → countersign. DocuSign handles signing but doesn't integrate with their CRM, can't enforce their approval chain, and the API costs extra. They need a signing system embedded in their app with custom workflows, audit trails, and multi-party signing order.

Step 1: Build the Signing Engine

typescript
// src/signing/engine.ts — Document signing with workflows and audit trails
import { randomBytes, createHash } from "node:crypto";
import { pool } from "../db";
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

type SigningStatus = "draft" | "pending" | "in_progress" | "completed" | "declined" | "expired";

interface SigningRequest {
  id: string;
  documentId: string;
  documentUrl: string;
  title: string;
  status: SigningStatus;
  signers: Signer[];
  fields: SigningField[];
  createdBy: string;
  expiresAt: string | null;
  completedAt: string | null;
  auditTrail: AuditEvent[];
}

interface Signer {
  id: string;
  name: string;
  email: string;
  role: string;
  order: number;              // signing order (1 = first)
  status: "waiting" | "notified" | "viewed" | "signed" | "declined";
  signedAt: string | null;
  signatureData: string | null;
  ipAddress: string | null;
}

interface SigningField {
  id: string;
  signerId: string;
  type: "signature" | "initials" | "date" | "text" | "checkbox";
  page: number;
  x: number;
  y: number;
  width: number;
  height: number;
  required: boolean;
  value: string | null;
}

interface AuditEvent {
  action: string;
  actor: string;
  actorEmail: string;
  ipAddress: string;
  userAgent: string;
  timestamp: string;
  details?: string;
}

// Create signing request
export async function createSigningRequest(
  documentUrl: string,
  title: string,
  signers: Array<{ name: string; email: string; role: string; order: number }>,
  fields: Array<Omit<SigningField, "id" | "value">>,
  createdBy: string,
  expiresInDays: number = 30
): Promise<SigningRequest> {
  const id = `sign-${Date.now()}-${randomBytes(4).toString("hex")}`;
  const expiresAt = new Date(Date.now() + expiresInDays * 86400000).toISOString();

  const signerRecords: Signer[] = signers.map((s, i) => ({
    id: `signer-${i}-${randomBytes(3).toString("hex")}`,
    ...s,
    status: s.order === 1 ? "notified" : "waiting",
    signedAt: null, signatureData: null, ipAddress: null,
  }));

  const fieldRecords: SigningField[] = fields.map((f, i) => ({
    ...f, id: `field-${i}`, value: null,
  }));

  await pool.query(
    `INSERT INTO signing_requests (id, document_url, title, status, signers, fields, created_by, expires_at, audit_trail, created_at)
     VALUES ($1, $2, $3, 'pending', $4, $5, $6, $7, $8, NOW())`,
    [id, documentUrl, title, JSON.stringify(signerRecords), JSON.stringify(fieldRecords),
     createdBy, expiresAt, JSON.stringify([{
       action: "created", actor: createdBy, actorEmail: "", ipAddress: "", userAgent: "",
       timestamp: new Date().toISOString(),
     }])]
  );

  // Send notification to first signer
  const firstSigner = signerRecords.find((s) => s.order === 1);
  if (firstSigner) {
    await sendSigningNotification(id, firstSigner);
  }

  return {
    id, documentId: id, documentUrl, title, status: "pending",
    signers: signerRecords, fields: fieldRecords, createdBy,
    expiresAt, completedAt: null, auditTrail: [],
  };
}

// Sign document
export async function signDocument(
  requestId: string,
  signerId: string,
  fieldValues: Record<string, string>,
  signatureData: string,
  metadata: { ip: string; userAgent: string }
): Promise<{ success: boolean; allSigned: boolean; nextSigner: Signer | null }> {
  const { rows: [request] } = await pool.query("SELECT * FROM signing_requests WHERE id = $1", [requestId]);
  if (!request) throw new Error("Signing request not found");
  if (request.status === "completed" || request.status === "expired") throw new Error("Request is closed");

  // Check expiry
  if (request.expires_at && new Date(request.expires_at) < new Date()) {
    await pool.query("UPDATE signing_requests SET status = 'expired' WHERE id = $1", [requestId]);
    throw new Error("Signing request has expired");
  }

  const signers: Signer[] = JSON.parse(request.signers);
  const fields: SigningField[] = JSON.parse(request.fields);
  const auditTrail: AuditEvent[] = JSON.parse(request.audit_trail);

  const signer = signers.find((s) => s.id === signerId);
  if (!signer) throw new Error("Signer not found");
  if (signer.status === "signed") throw new Error("Already signed");

  // Verify it's this signer's turn
  const previousSigners = signers.filter((s) => s.order < signer.order);
  const allPreviousSigned = previousSigners.every((s) => s.status === "signed");
  if (!allPreviousSigned) throw new Error("Waiting for previous signers");

  // Validate required fields
  const signerFields = fields.filter((f) => f.signerId === signerId);
  for (const field of signerFields) {
    if (field.required && !fieldValues[field.id]) {
      throw new Error(`Field "${field.type}" is required`);
    }
    field.value = fieldValues[field.id] || null;
  }

  // Update signer
  signer.status = "signed";
  signer.signedAt = new Date().toISOString();
  signer.signatureData = signatureData;
  signer.ipAddress = metadata.ip;

  // Audit
  auditTrail.push({
    action: "signed", actor: signer.name, actorEmail: signer.email,
    ipAddress: metadata.ip, userAgent: metadata.userAgent,
    timestamp: new Date().toISOString(),
    details: `Signed as ${signer.role}`,
  });

  // Check if all signed
  const allSigned = signers.every((s) => s.status === "signed");
  const newStatus = allSigned ? "completed" : "in_progress";

  // Notify next signer
  let nextSigner: Signer | null = null;
  if (!allSigned) {
    nextSigner = signers.find((s) => s.status === "waiting" || s.status === "notified") || null;
    if (nextSigner) {
      nextSigner.status = "notified";
      await sendSigningNotification(requestId, nextSigner);
    }
  }

  await pool.query(
    `UPDATE signing_requests SET
       status = $2, signers = $3, fields = $4, audit_trail = $5,
       completed_at = $6
     WHERE id = $1`,
    [requestId, newStatus, JSON.stringify(signers), JSON.stringify(fields),
     JSON.stringify(auditTrail), allSigned ? new Date().toISOString() : null]
  );

  if (allSigned) {
    await generateSignedPDF(requestId);
  }

  return { success: true, allSigned, nextSigner };
}

// Decline to sign
export async function declineToSign(
  requestId: string, signerId: string, reason: string,
  metadata: { ip: string; userAgent: string }
): Promise<void> {
  const { rows: [request] } = await pool.query("SELECT * FROM signing_requests WHERE id = $1", [requestId]);
  const signers: Signer[] = JSON.parse(request.signers);
  const auditTrail: AuditEvent[] = JSON.parse(request.audit_trail);

  const signer = signers.find((s) => s.id === signerId)!;
  signer.status = "declined";

  auditTrail.push({
    action: "declined", actor: signer.name, actorEmail: signer.email,
    ipAddress: metadata.ip, userAgent: metadata.userAgent,
    timestamp: new Date().toISOString(), details: reason,
  });

  await pool.query(
    "UPDATE signing_requests SET status = 'declined', signers = $2, audit_trail = $3 WHERE id = $1",
    [requestId, JSON.stringify(signers), JSON.stringify(auditTrail)]
  );

  // Notify creator
  await redis.rpush("email:queue", JSON.stringify({
    type: "signing_declined", to: request.created_by,
    signerName: signer.name, reason, requestId,
  }));
}

async function sendSigningNotification(requestId: string, signer: Signer): Promise<void> {
  const token = randomBytes(32).toString("urlsafe-base64");
  await redis.setex(`sign:token:${token}`, 86400 * 30, JSON.stringify({ requestId, signerId: signer.id }));

  await redis.rpush("email:queue", JSON.stringify({
    type: "signing_request", to: signer.email,
    signerName: signer.name, signingUrl: `${process.env.APP_URL}/sign/${token}`,
  }));
}

async function generateSignedPDF(requestId: string): Promise<void> {
  // Flatten signatures onto PDF and store final document
}

Results

  • DocuSign cost: $6K/year → $0 — self-hosted signing with the same audit trail, multi-party workflow, and legal compliance
  • Signing integrated into app workflow — contracts created from CRM data, signed, and status synced back automatically; no context switching
  • Average signing time: 3 days → 4 hours — embedded signing link in email, one click to review and sign; mobile-friendly
  • Audit trail exceeds legal requirements — every action logged with timestamp, IP, user agent; tamper-evident chain of events
  • Sequential signing enforced — legal reviews before client sees the document; client signs before countersign; no out-of-order signing