> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ringg.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> To search or read these docs, connect the Ringg docs MCP server at https://docs.ringg.ai/mcp (no key needed; tools search_ringg_ai and query_docs_filesystem_ringg_ai) instead of browsing pages one by one. Setup for each client: https://docs.ringg.ai/resources/mcp-server/docs-mcp-server/mcp-for-ai-assistants.md
> The dashboard labels agents "Assistants"; these docs say "agent". Both mean the same object, identified by agent_id.
> API base URL: https://prod-api.ringg.ai/ca/api/v0. Send the workspace API key in the X-API-KEY header, from server-side code only.
> Placing a call dials a real phone number and spends credits. Confirm the number with the user before calling.
> To act on a Ringg workspace (place calls, edit agents, read transcripts), use the separate Ringg workspace MCP server at https://mcp.ringg.ai/mcp: https://docs.ringg.ai/resources/mcp-server/workspace-mcp-server/overview.md

# Lesson: Run a campaign

> Launch Ananya's first campaign in Campaign Setup: upload a three-row list of numbers you own, pick the number pool, set retries and a calling window.

export const Quiz = ({question, answers, correctFeedback, incorrectFeedback}) => {
  const [selected, setSelected] = React.useState(null);
  const [checked, setChecked] = React.useState(false);
  const quizId = React.useId();
  const isCorrect = checked && answers[selected]?.correct;
  const reset = () => {
    setSelected(null);
    setChecked(false);
  };
  const optionStyle = (answer, i) => {
    const base = {
      display: "flex",
      alignItems: "flex-start",
      gap: "0.6rem",
      padding: "0.6rem 0.8rem",
      marginTop: "0.5rem",
      borderRadius: "0.6rem",
      border: "1px solid rgba(128, 128, 128, 0.35)",
      cursor: checked ? "default" : "pointer",
      lineHeight: 1.45
    };
    if (checked && answer.correct) {
      return {
        ...base,
        borderColor: "rgba(22, 163, 74, 0.8)",
        background: "rgba(22, 163, 74, 0.12)"
      };
    }
    if (checked && selected === i && !answer.correct) {
      return {
        ...base,
        borderColor: "rgba(220, 38, 38, 0.8)",
        background: "rgba(220, 38, 38, 0.1)"
      };
    }
    if (!checked && selected === i) {
      return {
        ...base,
        borderColor: "currentColor",
        background: "rgba(128, 128, 128, 0.12)"
      };
    }
    return base;
  };
  const buttonStyle = disabled => ({
    marginTop: "0.9rem",
    padding: "0.45rem 0.9rem",
    borderRadius: "0.5rem",
    border: "1px solid rgba(128, 128, 128, 0.5)",
    background: "rgba(128, 128, 128, 0.1)",
    color: "inherit",
    font: "inherit",
    fontSize: "0.9em",
    cursor: disabled ? "not-allowed" : "pointer",
    opacity: disabled ? 0.5 : 1
  });
  return <div style={{
    marginTop: "1.5rem",
    padding: "1rem 1.1rem",
    borderRadius: "0.8rem",
    border: "1px solid rgba(128, 128, 128, 0.35)"
  }}>
      <Badge color="green">Quiz</Badge>
      <p id={quizId + "-question"} style={{
    fontWeight: 600,
    margin: "0.6rem 0 0.2rem"
  }}>
        {question}
      </p>
      <div role="radiogroup" aria-labelledby={quizId + "-question"} aria-disabled={checked}>
        {answers.map((answer, i) => <label key={i} style={optionStyle(answer, i)}>
            <input type="radio" name={"quiz-" + quizId} checked={selected === i} onChange={() => !checked && setSelected(i)} disabled={checked} style={{
    marginTop: "0.3rem",
    flexShrink: 0
  }} />
            <span>{answer.text}</span>
          </label>)}
      </div>
      {checked && <div role="status" aria-live="polite" aria-atomic="true" style={{
    marginTop: "0.9rem",
    padding: "0.7rem 0.9rem",
    borderRadius: "0.6rem",
    lineHeight: 1.5,
    background: isCorrect ? "rgba(22, 163, 74, 0.12)" : "rgba(220, 38, 38, 0.1)"
  }}>
          <strong style={{
    marginRight: "0.4rem"
  }}>{isCorrect ? "✓" : "✗"}</strong>
          {isCorrect ? correctFeedback : incorrectFeedback}
        </div>}
      {!checked ? <button type="button" onClick={() => selected !== null && setChecked(true)} disabled={selected === null} style={buttonStyle(selected === null)}>
          Check answer
        </button> : <button type="button" onClick={reset} style={buttonStyle(false)}>
          Try again
        </button>}
    </div>;
};

export const RelatedTopics = ({items}) => <div className="not-prose mt-12">
    <div className="text-sm font-medium text-gray-500 dark:text-zinc-400 mb-3">Related topics</div>
    <div className="flex flex-col gap-2.5">
      {items.map(([title, href]) => <a key={href} href={href} className="flex items-center gap-2.5 text-sm text-gray-700 hover:text-gray-950 dark:text-zinc-300 dark:hover:text-white">
          <Icon icon="file-text" size={16} color="currentColor" />
          {title}
        </a>)}
    </div>
  </div>;

**Part 4: Taking your agent live · Lesson 2 of 4 · 20 minutes**

## Objective

Launch a campaign of three calls to numbers you own, and follow it from **Scheduled** to **Completed**.

## The scenario

Before Acme calls real customers, it runs the reminder over three internal numbers: yours and two colleagues who agree to answer. The flow is the same at thousands of rows. Make a CSV with the header row `callee_name,mobile_number,due_amount,due_date,loan_account` and three rows, phone numbers as ten digits without `+91`. Headers that match Ananya's variable names map themselves.

## Hands-on

<Frame caption="Creating a campaign in the dashboard">
  <video alt="Creating a campaign in the dashboard" aria-label="Creating a campaign in the dashboard" controls muted playsInline className="w-full rounded-xl" style={{ aspectRatio: "16 / 10" }} preload="metadata" controlsList="nodownload noremoteplayback" poster="/media/campaigns-create/poster.webp">
    <source src="https://mintcdn.com/ringgai-0da73d20/zhzhS2waLZvF8qXk/media/campaigns-create/video.webm?fit=max&auto=format&n=zhzhS2waLZvF8qXk&q=85&s=87ab1002ac94653cb50efb6024632e1d" type="video/webm; codecs=&#x22;av01.0.09M.08&#x22;" data-path="media/campaigns-create/video.webm" />
  </video>
</Frame>

<Steps>
  <Step title="Name it and pick the agent">
    In the sidebar, open **Monitor → Campaigns** and click **Create campaign**. **Campaign Setup** opens on **Upload details**. Enter **Campaign name** `EMI reminders - dry run`, keep **Campaign type** on **Assistant**, and pick **EMI reminder - Hindi** under **Select assistant**.
  </Step>

  <Step title="Upload the list">
    Drag your CSV onto **Upload calling list** or click **Select File**. In **Calling list settings**, leave **Country code** on **+91** and check that every variable under **Map variables to CSV columns** found its column. Leave **Remove invalid rows** on. Click **Next**: "Processing CSV..." then "CSV verified successfully".
  </Step>

  <Step title="Configure the calls">
    Under **Select number**, choose **Number pool** and pick **Acme EMI reminders**. Keep **Timezone** `Asia/Kolkata`; **Start date & time** defaults to 10 minutes from now and **End date & time** to a day later. **Calling windows** defaults to 08:00–20:00 every day; if you are outside those hours, click **Edit**, add a window that covers now, and **Save**. Set **Retry count** to **1 Retry** and **Cooldown Period** to **5 Minutes**. **Email notifications** already lists you.
  </Step>

  <Step title="Launch">
    Click **Create campaign**. You see "Campaign created successfully!" and the campaign appears in the list as **Scheduled**. The three calls are real calls.
  </Step>

  <Step title="Watch it run">
    Once the start time passes inside the calling window, your phones ring. Click the campaign's row: the detail panel shows **Status**, **Assistant**, **Call time**, **Timezone** and **Retry configuration**. **History** opens the campaign's calls in Logs, with no date limit.
  </Step>
</Steps>

## Checkpoint

<Check>
  When the attempts are done, the detail panel shows **Status** **Completed** with the buttons **Analytics**, **History** and **Export report**. **History** lists the three calls as **Completed**; a call you let ring first shows **Retry**, then **Completed** after the second attempt.
</Check>

## Exercise

Create a second campaign from the same file, then stop it before it starts: select its row and click **Terminate**. The dialog reads "Termination will stop all future calls. Are you sure you want to proceed?" Click **Confirm**. Expected result: "Campaign terminated", and because the campaign never placed a call it drops out of the list. There is no pause, and no resume from the dashboard.

## What you learned

* **Campaign Setup** has two steps: **Upload details** (name, agent, CSV, country code, variable mapping) and **Configure campaign** (numbers or pool, schedule, calling windows, retries, notifications).
* Calls go out only after the start time and inside a calling window, read in the campaign's timezone. Every retry is another call to the same person.
* The detail panel's **History** opens the campaign's calls in Logs; **Terminate** stops all future calls, and a terminated campaign can't be resumed from the dashboard.

<Quiz
  question="It is 21:30 in Asia/Kolkata. You create a campaign with the default calling windows and a start time 10 minutes from now. When do the calls go out?"
  answers={[
{ text: "At 21:40, because the start time wins", correct: false },
{ text: "Not until 08:00 tomorrow, because calls go out only inside a calling window", correct: true },
{ text: "Immediately, because the default window is all day", correct: false },
{ text: "Never; the campaign is rejected", correct: false },
]}
  correctFeedback="Right. Calls go out only after the start time and inside a calling window, read in the campaign's timezone. The default window is 08:00–20:00 every day, so a 21:40 start waits until 08:00. To run now, edit the windows to cover the current time."
  incorrectFeedback="Not quite. Both conditions must hold: the start time has passed and the clock is inside a calling window. The default windows are 08:00–20:00 in the campaign's timezone, so an evening start waits for the next morning unless you add a window."
/>

<RelatedTopics items={[["Create a campaign", "/documentation/deploy/campaigns/create-a-campaign"], ["Manage a campaign", "/documentation/deploy/campaigns/manage-a-campaign"]]} />


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.