# Summarize every conversation and email the results, automatically

[← Back to Lifecycle](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md)


How to turn "someone has to read every transcript and decide what matters" into "the backend tells you, automatically, the moment a conversation ends." Two lifecycle rules do the whole job, with zero polling and zero app-side glue. One rule extracts a summary and topic the instant a session ends. A second rule emails a human the instant that extraction finishes. This recipe is the hands-on walkthrough; [Lifecycle Rules](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md) is the terse field-by-field reference this recipe links back to instead of repeating.

---

## The mental model

Three pieces, always in this order:

| Piece | What it is | Example |
|---|---|---|
| **Event** | Something the backend already noticed happened to a `thread` | `session_ended` (a conversation just ended), `analysis_updated` (an insight just got written) |
| **Rule** | `{eventType, objectType, eventConditions?, action}` you create once via `mgmt.lifecycle.create()` | "when a `session_ended` fires, run this action" |
| **Action** | What runs automatically, server-side, when the rule matches | `triggerInsightSettingsKai` (extract structured data with an LLM, per a reusable `InsightSettings` definition) or `sendInsightEmail` (email a human) |

The two actions chain naturally. `triggerInsightSettingsKai` writes its results into the thread's `thread_metadata.analysis`. That write is itself an `analysis_updated` event, which a second rule can react to. That's the whole recipe below: rule 1 reacts to `session_ended` and produces analysis, rule 2 reacts to `analysis_updated` and emails it.

---

## Recipe A: Extract a summary the moment a session ends

`triggerInsightSettingsKai` doesn't take the insight's definition inline. It references one or more [`InsightSettings`](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#insightsettings-reusable-insight-definitions) entities by id. Define those first:

```js
const topic = await mgmt.insightSettings.create({
  key: 'TOPIC', title: 'Topic', prompt: 'What was the main topic of this conversation, in 1-3 words?', valueType: 'string',
}, adminKs);
const nextStep = await mgmt.insightSettings.create({
  key: 'CUSTOM', title: 'Next step', prompt: 'One actionable next step for the support team, or "none".', valueType: 'string',
}, adminKs);
```

Then reference both from the rule:

```js
await mgmt.lifecycle.create({
  name: 'Summarize on session end',
  systemName: 'auto_summary_v1',
  eventType: 'session_ended',
  objectType: 'thread',
  eventConditions: [{ field: 'object.agent_id', operator: 'eq', value: '<agent-uuid>' }],
  action: { actionType: 'triggerInsightSettingsKai', insightSettingsIds: [topic.id, nextStep.id] },
}, adminKs);
```

Notice there's no `SUMMARY` insight setting here. Every partner already gets one for free, see [`Lifecycle Rules`'s action-type table](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#the-action-types) for why. `TOPIC` and `CUSTOM` are both included here because Recipe B's email preset needs all three of `SUMMARY`/`TOPIC`/`CUSTOM` present, see the gotcha below.

`prompt` is required on **every** `InsightSettings` entity. There's no built-in fallback prompt for any key name, including `TOPIC` or `CUSTOM`. `valueType` is required too. Omit either and `insightSettings.create` 400s. Every conversation gets a structured recap with zero app-side code: no cron job polling for "threads that just ended," no app server involved at all.

---

## Recipe B: Email a human the moment that analysis lands

```js
await mgmt.lifecycle.create({
  name: 'Email support lead on analysis update',
  systemName: 'analysis_alert_v1',
  eventType: 'analysis_updated',
  objectType: 'thread',
  eventConditions: [
    { field: 'object.agent_id', operator: 'eq', value: '<agent-uuid>' },
    { field: 'changed_keys', operator: 'has_all', value: ['SUMMARY', 'TOPIC', 'CUSTOM'] },
  ],
  action: {
    actionType: 'sendInsightEmail',
    recipients: ['support-lead-kaltura-user-id'],
    presetType: 'conversationInsightExample',
  },
}, adminKs);
```

Three things about this action that aren't obvious from the field names:

1. **It only fires on `analysis_updated`.** Attach it to a `session_ended` rule and it does nothing. Nothing errors, nothing sends.
2. **`recipients` are Kaltura user IDs, not raw email addresses.** The email address comes from that user's Kaltura profile (`{USER.email}`). If your account's convention is to use the email address itself as the Kaltura user ID (common on many accounts), a recipient string that looks like an email still works. That's only because it's also a valid user ID there, not because this field accepts arbitrary email strings.
3. **`presetType: 'conversationInsightExample'` is the zero-setup path.** It needs no template setup. An explicit `templateId` (instead of `presetType`) points at a template you author yourself via [`mgmt.emailTemplates`](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#emailtemplates-managing-the-templates-sendinsightemail-references), with your own subject, body, and branding, instead of the preset's fixed layout.

### The gotcha that will bite you first: token mismatch

`conversationInsightExample`'s template needs three insight values by key: **`SUMMARY`, `TOPIC`, and `CUSTOM`** (exactly those keys, case-sensitive). `AGENTNAME`, `CTAURL`, and `USER` are filled in automatically, so you never provide those. If the thread's analysis doesn't have all three of `SUMMARY`/`TOPIC`/`CUSTOM`, the email send is skipped, and nothing surfaces to your app or the SDK. `SUMMARY` comes free from the always-on system preset (see [Lifecycle Rules](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#every-session-already-gets-a-summary-for-free)). Recipe A's own `InsightSettings` above supply the other two, with `key:'TOPIC'` and `key:'CUSTOM'` matching exactly what the template looks for.

Pick whatever `prompt` fits your use case for `CUSTOM`. The template only cares about the `key`, never the prompt text.

---

## Scoping the alert to one agent

Both recipes use `eventConditions` with `object.agent_id`. The SDK requires it: see [Scope every rule to an agent](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#scope-every-rule-to-an-agent). The rule only matches conversations started with a KS that carries the agent id. A token minted without one leaves every thread's `agent_id` as `"default"`, so it can never match. Mint with `createAgentToken({ agentId })` (see [Conversation token or agent token?](https://kaltura.github.io/intelligent-agents-sdk/reference/api/authentication/index.md#conversation-token-or-agent-token)), and see [`Lifecycle Rules`'s scoping section](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#scoping-a-rule-to-one-agent) for the full explanation.

---

## Reading the results back

Once `triggerInsightSettingsKai` finishes (it runs asynchronously, expect a short delay, not instant), the values land in the thread's `thread_metadata.analysis`, keyed by each `InsightSettings.key`:

```js
const thread = await mgmt.threads.get(threadId, adminKs);
console.log(thread.thread_metadata.analysis); // { SUMMARY: '...', TOPIC: '...', CUSTOM: '...' }
```

You only need this for a dashboard or a "show me the recap" UI. If all you want is the email, Recipe B already handles delivery. You don't need to read this back yourself.

---

## Test both rules in seconds, without waiting for a real event

Waiting for a real conversation to end and a real analysis to land is not how you iterate on rule design. `mgmt.lifecycle.match()` answers "if this event happened right now, which rules would fire?" against data you make up, instantly, with no thread and no waiting:

```js
const { matchedRules } = await mgmt.lifecycle.match(
  'thread', 'session_ended',
  { object: { agent_id: 'agent-1', thread_id: 'thread-1', user_id: 'user-1' } },
  adminKs,
);
```

`object.agent_id`, `object.thread_id`, and `object.user_id` are all required strings for `objectType:'thread'`. Some environments also require `object.origin` (a number, `0` = standard). Pass every `object.*` field that `describeFields` reports for your environment. Omit one and it 400s naming the missing path. Expect to see your own rule nested inside a grouped `matchedRules[]` entry's `rules[]` array, together with the built-in summary rule.

Every partner has that built-in rule by default. It's not something you configured (see [`Lifecycle Rules`'s note on grouped matches](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#discovery-and-dry-run-testing)). Run this after creating each rule to confirm it matches before you ever touch a real conversation.

---

## Minimal runnable example

[`examples/lifecycle-insights-and-email.mjs`](https://github.com/kaltura/intelligent-agents-sdk/blob/main/examples/lifecycle-insights-and-email.mjs) creates the `InsightSettings` and both rules above, dry-run tests each with `match()`, lists and inspects them, then cleans up. All against the real API, no waiting for a real session to end:

```bash
export AGENTIC_PARTNER_ID=1234567
export AGENTIC_ADMIN_SECRET=your_admin_secret_here
node examples/lifecycle-insights-and-email.mjs
```

---

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| `insightSettings.create` 400s: `valueType must be one of...` | `valueType` omitted or not one of the supported types | Add a valid `valueType` to every `InsightSettings` entity |
| `lifecycle.create` 400s: `action.insightSettingsIds.0 not found` | An id in `insightSettingsIds` doesn't exist, or belongs to a different partner | Create the `InsightSettings` entity first and use the id it returns |
| A rule references a real `insightSettingsIds` id but that insight never gets extracted | The referenced `InsightSettings` entity has `status:'disabled'` | `mgmt.insightSettings.update(id, {status:'active'}, ks)` |
| `sendInsightEmail` rule never sends anything, no error anywhere | The paired `triggerInsightSettingsKai` rule doesn't produce every key the preset needs | Match Recipe A's `InsightSettings.key` values to the preset's requirements exactly (see the gotcha above) |
| A `sendInsightEmail` rule attached to `session_ended` does nothing | That action only fires on `analysis_updated` | Change `eventType` to `analysis_updated` |
| `lifecycle.create` or `update` throws `lifecycle_unscoped` | A `thread` rule has no `object.agent_id` condition (`eq` with an id, or `in` with a non-empty array of ids), so it would run for every agent on the partner | Add `{field:'object.agent_id', operator:'eq', value:'<agent-uuid>'}`, or pass `{ partnerWide: true }` to run on every agent on purpose |
| `lifecycle.create` or `update` throws `lifecycle_email_unfiltered` | A `sendInsightEmail` rule on `analysis_updated` has no `changed_keys` condition, so it would send on every analysis update | Add `{field:'changed_keys', operator:'has_all', value:['SUMMARY', ...]}`, or pass `{ emailOnEveryUpdate: true }` to send on every update on purpose |
| `eventConditions` on `object.agent_id` never matches | The thread was created with a token minted without an agent id | Mint with `mgmt.sessions.createAgentToken({agentId})` |
| `lifecycle.match` 400s: `eventData.object.user_id: Invalid input...` | A required field missing from the dry-run `object` | Pass `agent_id`, `thread_id`, and `user_id` together, plus `origin: 0` where `describeFields` lists `object.origin` |
| An `InsightSettings` entity 400s or its rule silently produces nothing | No `prompt` supplied | `prompt` is required on every `InsightSettings` entity, there's no built-in fallback for any key |
| You want to change the built-in `SUMMARY` insight's prompt | It has no customization lever, no field on any entity changes it | Give your own insight settings distinct `key`s and use those instead (see [Lifecycle Rules](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#every-session-already-gets-a-summary-for-free)) |
| A rule saved fine but nothing happens, or you inherited a partner's rules | A setting, template or agent the rule points at was deleted or disabled after it was saved | Run `mgmt.lifecycle.audit(ks)`. Every finding names the rule and the fix. See [Audit your rules](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md#audit-your-rules) |
| You create a `triggerDtcKai` rule and expect to pass fields on the action | It takes no caller-supplied fields, it derives insights from the target intellect's configured lead-capture form fields | Configure `intellectConfig.user_properties_forms` on the intellect instead; leave the action `{actionType:'triggerDtcKai'}` |

---

## Related docs

| Doc | What it adds |
|---|---|
| [Lifecycle Rules](https://kaltura.github.io/intelligent-agents-sdk/reference/lifecycle/index.md) | The full field-by-field reference: every rule shape, the action types, `InsightSettings`, the full CRUD + discovery method table |
| [`examples/lifecycle-insights-and-email.mjs`](https://github.com/kaltura/intelligent-agents-sdk/blob/main/examples/lifecycle-insights-and-email.mjs) | The runnable example this recipe walks through |
| [Getting Started](https://kaltura.github.io/intelligent-agents-sdk/getting-started/index.md) | Where `configId`/`agentId` and the admin token in the examples above come from |

