Skip to main content
Technical Tutorials

Preserve a CTA promise through the contact form

A specific article CTA can still land on generic form copy. Define one intent route that keeps the promise, attribution, form state, and fallback consistent.

Sean McLellan profile photo

Sean McLellan

Lead Architect & Founder

8 min read
Constructed diagram connecting the Review Code Quality scope article CTA and its four URL values to an exact-intent-first resolver, matched visible and hidden form state, and an unchanged generic contact fallback.
Constructed diagramConstructed diagram based on the verified public Code Quality path. The exact intent resolves before the generic blog fallback; the verification did not submit the form.

A BaristaLabs article ended with a specific call to action, or CTA: Review Code Quality scope. The link sent the request to a shared contact page and carried four useful values: the request intent, the source, the article slug, and the CTA placement. The first version of the destination ignored most of that promise. It showed generic article-follow-up copy, left the service unselected, used a generic message prefill, and displayed an unrelated quote-approval example.

The link worked, and its attribution values survived. The page experience still lost the reason that the visitor selected the CTA. A developer, marketing-operations lead, or technical owner can prevent this mismatch by defining one small routing contract. The contract states which URL inputs the form receives, which visible state it sets, which hidden context it keeps, and which generic behavior must remain unchanged.

A routing contract is the expected relationship between a source link and its destination. It gives the implementation and the test the same set of facts. For a shared contact form, the contract needs six parts.

Scroll sideways to see all 3 columns.

PartWhat to specifyWhy it matters
Visible intentHeading, explanation, example, and submit labelThe destination acknowledges the action that the visitor selected.
Source attributionIntent, source, article slug, and placementAnalytics and routing keep the origin of the request.
Relevant form stateSelected service and field labelsThe form starts in the correct service context.
Safe prefillAn editable structure for the requested informationThe visitor does not have to reconstruct the request from an empty field.
Hidden routingVariant, lead source, recommended service, and related flagsDownstream systems receive the same context that the page displayed.
Generic fallbackThe state for /contact and unknown intentsOne specific route does not replace the form for every other visitor.

Keep these values in one route definition when the application structure permits it. If the heading comes from one component, the selected service from another, and the hidden fields from a third unrelated condition, the page can show one promise and submit another. A single route definition makes those states easier to review together.

Resolve the specific intent before the general blog fallback

A shared form often has a broad rule such as source=blog. That rule is useful for an article with a general contact CTA. It is too broad when an article offers a named review or implementation path. The resolver must check the exact intent first and use the general blog treatment only when no specific route matches.

The following constructed TypeScript example shows the order. It is an implementation pattern, not code copied from the BaristaLabs repository.

type ContactView = {
  heading: string;
  service: string | null;
  messageTemplate: string;
  variant: string;
  sensitiveDataNotice: boolean;
};

const intentViews: Record<string, ContactView> = {
  "github-code-quality-scope-reconciliation": {
    heading: "Review one GitHub Code Quality scope change",
    service: "ai-consulting",
    messageTemplate: [
      "We want to review one GitHub Code Quality scope change.",
      "",
      "Organization or enterprise:",
      "Repository:",
      "Scope change or review window:",
      "Audit event available:",
      "Current Code Quality setting:",
      "Billing or usage question:",
      "Repository or scope owner:",
      "Decision needed:",
    ].join("\n"),
    variant: "github_code_quality_scope_reconciliation",
    sensitiveDataNotice: true,
  },
};

const genericArticleView: ContactView = {
  heading: "Ask about this article",
  service: null,
  messageTemplate: "I read the article and want to discuss:",
  variant: "blog_article_context",
  sensitiveDataNotice: true,
};

const genericContactView: ContactView = {
  heading: "Tell us about the work",
  service: null,
  messageTemplate: "",
  variant: "generic_contact",
  sensitiveDataNotice: true,
};

function resolveContactView(url: URL): ContactView {
  const intent = url.searchParams.get("intent") ?? "";

  if (intentViews[intent]) {
    return intentViews[intent];
  }

  if (url.searchParams.get("source") === "blog") {
    return genericArticleView;
  }

  return genericContactView;
}

The intent value selects an approved route definition. Do not use arbitrary query text as page copy, a service identifier, or an internal destination. Validate the values that control behavior. Keep source, slug, and placement as bounded attribution values, and treat every value in the URL as public.

Constructed diagram tracing the Review Code Quality scope article CTA and its intent, source, slug, and placement values into an exact-intent-first resolver. The matched route sets the visible Code Quality heading, service, editable note, safety notice, and hidden routing context, while the generic contact path remains unchanged.
Constructed diagramConstructed diagram. The verified public path checks the exact intent before the generic blog fallback, keeps visible and hidden form state aligned, and leaves the generic contact route unchanged. The verification did not submit the form.

The Code Quality route changed the destination, not the source promise

The Code Quality audit-event article already had a specific CTA and an attributed URL. The original production review found the failure after navigation. The destination returned HTTP 200, but its H1 asked a generic article question. Service Interest was blank, the message asked only about applying the article, and the page showed Customer update after quote approval as its main example.

The approved change kept the CTA label and its four query values. It added a destination for the exact Code Quality intent. The current route acknowledges one scope change, selects Strategic AI Consulting, provides an editable Code Quality review note, shows a sensitive-data notice, and replaces the unrelated example. It also keeps the route variant, lead source, service recommendations, and notice state in hidden form values. The generic contact page retains its general heading, unselected service, and workflow example.

A separate production review tested the source-to-destination path with Chromium and Playwright at 1440×1000 and 390×844. The reviewer activated the CTA by keyboard and did not submit the form. Both paths kept the exact query values and matched the approved heading, selected service, prefill, warning, hidden fields, labels, and focus behavior. The review found no horizontal overflow, console errors, page errors, failed requests, or HTTP error responses. A separate check confirmed that the generic contact route had not changed.

This evidence shows that the tested route behaved as specified on desktop and mobile. It does not show that the change increased conversions, improved lead quality, reduced cost, or produced revenue. Those outcomes need analytics and an agreed baseline.

Useful prefill gives structure without collecting private records

A good prefill reduces repeated typing and helps the visitor send the information needed for the promised next step. It should remain editable. The Code Quality route asks for high-level items such as the repository, review window, available audit event, current setting, billing question, owner, and decision needed. That structure matches the article without copying a private audit record into the form.

Keep sensitive material out of the URL and prefill. Do not request credentials, source code, raw logs, billing exports, personal data, customer records, or other private content in a general contact form. Ask for field names, record types, systems, owners, and the decision that the review must support. Move private artifacts to an approved channel after the first contact when the work requires them.

The same rule applies to automatic prefill. Build the template from approved static text. Do not copy an arbitrary query value into the message field. If a campaign, article, or tool name must appear, resolve it through a known identifier and escape it before rendering.

Test the route from the source CTA and leave the form unsubmitted

A component test can confirm the resolver output. It cannot prove that the published article points to the correct URL or that the browser renders the intended form state. Add an end-to-end test that starts at the source article, activates the CTA, and inspects the destination without sending a message.

This constructed Playwright test uses the public Code Quality path as an example:

import { expect, test } from "@playwright/test";

const expectedUrl =
  "https://www.baristalabs.io/contact" +
  "?intent=github-code-quality-scope-reconciliation" +
  "&source=blog" +
  "&slug=github-code-quality-audit-events-billing-scope" +
  "&placement=end";

test("keeps the Code Quality CTA context", async ({ page }) => {
  await page.goto(
    "https://www.baristalabs.io/blog/" +
      "github-code-quality-audit-events-billing-scope",
  );

  const cta = page.getByRole("link", {
    name: "Review Code Quality scope",
  });

  await cta.focus();
  await page.keyboard.press("Enter");

  await expect(page).toHaveURL(expectedUrl);
  await expect(
    page.getByRole("heading", {
      level: 1,
      name: "Review one GitHub Code Quality scope change",
    }),
  ).toBeVisible();
  await expect(page.getByLabel("Service Interest")).toHaveValue(
    "ai-consulting",
  );
  await expect(
    page.getByLabel("Code Quality scope review note *"),
  ).toHaveValue(/Decision needed:/);
  await expect(
    page.getByText("Do not submit raw audit-log exports"),
  ).toBeVisible();

  const expectedHidden = {
    intent: "github-code-quality-scope-reconciliation",
    source: "blog",
    slug: "github-code-quality-audit-events-billing-scope",
    placement: "end",
    contact_variant: "github_code_quality_scope_reconciliation",
    lead_intent: "github-code-quality-scope-reconciliation",
    lead_source: "blog",
    recommended_service: "ai-consulting",
    secondary_service: "process-automation",
    preselected_service: "true",
    intent_source: "article_specific",
    sensitive_data_notice_shown: "true",
  };

  for (const [name, value] of Object.entries(expectedHidden)) {
    await expect(page.locator(`input[name="${name}"]`)).toHaveValue(value);
  }

  // Do not select the submit button in this production-path test.
});

Use a separate regression test for the fallback. Open /contact without query parameters and confirm its generic H1, example, service state, field label, and submit label. Add another case for an unknown intent if the application accepts one. The expected result must be the approved fallback, not a partially applied specific route.

The browser review still needs checks that the assertion code can miss. Use the keyboard to reach the service field, message field, and submit button. Confirm that each control has a programmatic label and a visible focus style. Check the page at the target desktop and mobile widths for clipping, overlap, and horizontal overflow. Record console errors, page errors, failed requests, and HTTP error responses. Keep the form unsubmitted unless the test environment has an approved test destination and cleanup procedure.

Start with one explicit route and one unchanged fallback

Choose one high-intent CTA whose shared destination currently falls back to generic copy. Write the expected URL values, heading, service state, prefill, hidden context, and generic fallback before changing the resolver. Then place the exact intent ahead of the broad source rule and test the published path without submitting the form.

If this work is part of a website or form build, review AI-Assisted Website Development. If the form must send the same context into an inbox, CRM, or another system, review Process Automation & Integration. Keep the first release small: one CTA, one intent, one fallback, and one verified path.

Sources

BaristaLabs controls the routing recommendations, constructed code examples, and test guidance in this tutorial. The before-and-after case comes from first-party content, implementation, and production-review records. The production review did not submit the form and does not establish a lead or revenue outcome.

Contact routing review

Keep one article CTA specific through the shared form

Bring the CTA label, destination URL, expected form state, attribution fields, and generic fallback. BaristaLabs can help define the smallest route and test path.

Start with one CTA, one exact intent, one connected-system handoff, and one unchanged fallback.

Turn this idea into a pilot

Which workflow should go first?

Use the readiness check to compare impact, effort, risk, owner, and next step before requesting a review.

  • 3-5 minutes
  • Deterministic score
  • No sensitive data
Check workflow readiness

Practical AI Workflow Notes

Want more practical AI operations ideas?

Get short notes on applying AI inside real small-business workflows — from document handling and customer follow-up to internal reporting, compliance, and automation guardrails.

A useful next step if you’re still exploring and not ready to request a 20-minute workflow assessment.

Occasional emails. Practical workflow guidance only. Unsubscribe anytime.