SpectreSkills
← All skills

form-integrations

v1.0.0

Deliver a saved form submission to outside systems through a catalog of connectors. Trigger when adding a destination such as a spreadsheet, email list, CRM, or webhook, or when wiring what happens after someone submits.

Formsformsintegrationswebhooks
Install
npx @spectre-apps/skills add form-integrations

Writes .claude/skills/form-integrations/SKILL.md. Add --user to install globally.

What it does

Form integrations

A submission is saved first. Connectors run after that write commits. A connector failure is logged and retried; the respondent still sees the success message, because their submission is already stored.

FormDefinition.destination (see form-definition) names one catalog key, or is null when the only target is an email notification. "Email the team" is a flag on the form, notifyOnSubmission, and can run alongside a destination.

Catalog

Each destination is a manifest. Keys are stable and persisted; renaming one is a migration.

interface IntegrationManifest {
  key: string;                 // "webhook", "klaviyo", "googleSheets"
  name: string;
  initials: string;            // two letters on the tile
  description: string;
  category: "destination" | "analytics" | "notify";
  canReceiveSubmissions: boolean;
  fields: Array<{
    name: string;
    label: string;
    type: "text" | "secret" | "multiline";
    placeholder: string;
    help: string;
    required: boolean;
  }>;
}

The form's destination picker lists entries with canReceiveSubmissions. Analytics pixels and chat notifications live in the same catalog and stay out of that picker.

A workable starting set, all ordinary destinations behind one interface:

KeyCredentialsEffect
webhookURL, optional Authorization headerPOST the submission JSON
googleSheetsservice account, spreadsheet idappend a row
klaviyoAPI key, list idupsert a profile onto the list
mailchimpAPI key, audience idsubscribe, map merge fields
hubspotprivate app tokencreate or update a contact
zapiercatch-hook URLPOST, and let Zapier route it

A host-specific trigger (a commerce-platform flow, a CMS event) is another manifest and another module. It uses this seam.

Credentials

Credentials belong to a connection the account owns, not copied onto each form. The form stores the connection id (or the destination key plus the connection). secret fields are write-only: the UI shows a masked placeholder once set and never returns the value to the client. multiline is for private keys.

Dispatch

interface SubmissionRecord {
  id: string;
  formId: string;
  locale: string | null;
  fields: Record<string, string | string[]>;
  createdAt: string;
}

interface Connector {
  key: string;
  deliver(submission: SubmissionRecord, credentials: Record<string, string>): Promise<void>;
}

One module per key, registered in a map the dispatcher looks up. Adding a connector adds a manifest and a module; the submit handler stays as it is.

Order on POST /submit:

  1. Validate the body against the published definition.
  2. Insert the submission row.
  3. Respond to the embed.
  4. Deliver to the form's destination and send the notification email. Each call is independent. Record the outcome (pending, delivered, failed, next retry) on the submission.

Field keys in fields are the form's name values. A connector maps those keys onto the vendor's properties at delivery time (sheet columns, profile properties, contact fields). Leave the stored submission in the form's own keys.

Email notification

The notification is the path that needs no third-party account: a message to the addresses on the form, with the submission rendered as labeled rows. It runs through the same post-commit dispatch, so a mail outage does not fail the submit response.

Pair with form-web-component for the request that creates the submission and form-definition for the destination key and the warning when a form has nowhere to send.