> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/governance/certification/configure-the-review-workflow.md).

# Configure the review workflow

Configure who reviews a campaign, in what order, how long they get, what happens when the time runs out — and enable the two batch tasks without which no reminder is ever sent.

## What this is, and when you need it

Who is asked to decide, in what order, how long they get, what happens when the time runs out, and whether anyone is reminded. Four things on two tabs — **Reviewers** and **Expiration Policy** — plus two batch tasks that live somewhere else entirely and that ship **disabled**, which is why reminders and escalation usually do not happen.

A certification with no reviewer step cannot run at all: generation refuses it with *"Certification was not started because reviewers are empty"* in the `certification-manager` log, and a preview reports every user excluded with `NO_APPROVER_ASSOCIATION`.

## At a glance

|                          |                                                                                                                                                                                                          |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | Ordered `CERTIFICATION_REVIEWER` steps with a derived completion window, an escalation chain, an on-expiry action, and reminders that actually fire                                                      |
| **Where**                | `/webconsole/access-certification-edit?id=<CERTIFICATION_ID>` → **Reviewers** and **Expiration Policy** tabs; the two batch tasks are at **Administration → Batch Tasks**                                |
| **You need first**       | The certification type already chosen (it decides which reviewer types are offered); the relationships the type resolves against — supervisor, entitlement owner, application owner — actually populated |
| **Takes**                | 20 to 30 minutes, plus enabling two batch tasks once per deployment                                                                                                                                      |
| **Reversible**           | Yes on the definition. **No** for a running campaign: reviewer steps are resolved into `CAMPAIGN_STEP` and `CAMPAIGN_REVIEWERS` at launch and editing the definition does not change a run under way     |

## Before you begin

{% hint style="danger" %}
**Both reminder batch tasks ship disabled. Nothing you configure on these tabs has any effect until you enable them.** `REQUEST_REMINDERS` and `REQUEST_ESCALATION` both ship `ENABLED='N'`, and a campaign whose reviewers are silent, whose steps never escalate, and whose items never expire, on a system where both rows still read `ENABLED='N'`, is behaving exactly as configured. Step 5 below enables them.
{% endhint %}

Run a preview before you launch and `NO_APPROVER_ASSOCIATION` will be on the Excluded tab if you have configured no reviewer at all — it is the cheapest possible check.

## Configure it

{% stepper %}
{% step %}

### Add the first reviewer step

**Reviewers** tab → add a step. **Reviewer Type** offers a subset of values that depends on the certification type — pick the type first, then open the dropdown, and only the rows marked for that type below will be there.

| Value                   | Screen label                  | Offered for        | Resolves to                              | Fails as                             |
| ----------------------- | ----------------------------- | ------------------ | ---------------------------------------- | ------------------------------------ |
| `SUPERVISOR`            | User's Supervisor             | both types         | The reviewed user's supervisor           | `SUPERVISOR_NOT_FOUND`               |
| `ORGANIZATION`          | Organization Certifier        | both types         | The certifier of the user's organization | `ORGANIZATION_CERTIFIER_NOT_FOUND`   |
| `USER`                  | Select Reviewer               | both types         | One named user                           | `USER_NOT_FOUND`                     |
| `TARGET_USER`           | User Reviews Their Own Access | both types         | The reviewed user themselves             | —                                    |
| `GROUP`                 | Group                         | user certification | Every member of a named group            | `GROUP_REVIEWER_EMPTY`               |
| `SERVICE_ACCOUNT_OWNER` | Service Account Owner         | user certification | The owner of the service account         | `OWNER_OF_SERVICE_ACCOUNT_NOT_FOUND` |
| `APPLICATION_OWNER`     | Application Owner             | application        | Owner of the application resource        | `APPLICATION_OWNER_NOT_FOUND`        |
| `APPLICATION_ADMIN`     | Application Admin             | application        | Admin of the application resource        | `APPLICATION_ADMIN_NOT_FOUND`        |
| `ENTITLEMENT_OWNER`     | Entitlement Owner             | application        | Owner of the role or group               | `ENTITLEMENT_OWNER_NOT_FOUND`        |
| `ENTITLEMENT_ADMIN`     | Entitlement Admin             | application        | Admin of the role or group               | `ENTITLEMENT_ADMIN_NOT_FOUND`        |

The last five are certification-only. They are not offered on the ordinary approver-association screen used for access requests, and they are not treated as notifiable approvers there. That is intentional, not a missing option — do not go looking for **Entitlement Owner** on a request approval flow.

An unresolvable reviewer does **not** fail loudly. It removes the affected users from the campaign as a Stage 2 exclusion, recorded in `CAMPAIGN_PREVIEW_EXCLUSION` with category `REVIEWER`. Read `reviewersNotResolvedCount` on the preview before you launch.

{% hint style="warning" %}
`TARGET_USER` means the user attests to their own access. Self-attestation is a legitimate first step in some frameworks and worthless as the *only* step in most. If you use it, add a second mandatory step with an independent reviewer.
{% endhint %}
{% endstep %}

{% step %}

### Set the cadence, which sets the window

Decide the window you want, then pick a cadence that multiplies to it. `DAYS_TO_COMPLETE` is **not** an independent field:

{% code overflow="wrap" %}

```
daysToComplete = daysBeforeReminder × (numberOfReminds + 1)
```

{% endcode %}

A 21-day window with weekly nudges is `daysBeforeReminder = 7`, `numberOfReminds = 2` → 21. You cannot have a 30-day window with a single reminder on day 7 — the model does not express it.

New reviewer steps are seeded `DAYS_BEFORE_REMINDER` 1, `NUMBER_OF_REMINDERS` 1, `DAYS_TO_COMPLETE` 2, mandatory yes, and the next free step level. Two days is a placeholder, not a recommendation.

**Confirm it:** set the cadence, save, reopen the step, and read the window back off the screen. It should be the product of the two, and if it is not, one of the two cadence fields is empty — the derivation runs **only when both** have a value.
{% endstep %}

{% step %}

### Add later steps and the escalation chain

Add a second mandatory step with an independent reviewer wherever the first is `TARGET_USER`, or wherever a second pair of eyes is the control. Each step's escalation chain is `CERTIFICATION_REVIEWER_ESCALATION`, with its own `ESCALATION_LEVEL`, entity type and id, and the same four timing columns — so an escalation level has its own derived window.

The campaign's `EXPECTED_COMPLETED_DATE` is the first step's `DAYS_TO_COMPLETE` added to the execution date, while the **Reviewers** tab shows the sum across all steps as *Total Days for Escalation*. Those two numbers differ by design on a multi-step review, and neither is wrong.
{% endstep %}

{% step %}

### Set the expiration policy

**Expiration Policy** tab. Four fields on `CERTIFICATION`:

| Field            | Column                                        | Default      | Meaning                                                        |
| ---------------- | --------------------------------------------- | ------------ | -------------------------------------------------------------- |
| Action on expire | `ON_EXPIRE_ACTION VARCHAR(64)`                | `DO_NOTHING` | What happens to an undecided item                              |
| (implicit)       | `ALLOW_EXPIRING CHAR(1) NOT NULL DEFAULT 'Y'` | `true`       | Whether requests may expire at all                             |
| Extension days   | `MAX_EXTENSION_EXP_DAYS INT`                  | null         | Days an administrator may add; **capped at 365** by the screen |
| (implicit)       | `ALLOW_EXPIRING_EXTENSION CHAR(1)`            | `false`      | Whether extension is permitted                                 |

**Action on expire** accepts three values:

| Value                                | Screen label                       | Effect on expiry                                                                                  |
| ------------------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------- |
| `DO_NOTHING`                         | Do nothing                         | Nothing is revoked. Items end as `NOT_REVIEWED` with a decision source of `NONE`. **The default** |
| `REVOKE_ALL_ACCESS`                  | Revoke all access                  | Every in-scope role and group for that user is revoked                                            |
| `REVOKE_ONLY_ALREADY_REVOKED_ACCESS` | Revoke only already revoked access | Only items a reviewer had already marked revoked are actioned                                     |

An empty value behaves as `DO_NOTHING`. Revocations the policy performs are stamped with the system user id (`0001`) in `ACCESS_REVIEW_ITEM.ACCESS_REVOKED_BY`, an outcome of `REVOKED`, and an `ACCESS_REVIEW_ITEM_AUDIT` row whose `AUDIT_SPECIFICATION` is `CANCELED_AS_EXPIRED`. **The revoking actor is how you prove afterwards whether a human or the clock revoked an entitlement** — the system user id means the policy did it, any other id means a reviewer did.

{% hint style="warning" %}
Do not look for this in `ACCESS_REVIEW_ITEM_AUDIT.DECISION_SOURCE`. That column records how a decision was *submitted* — `INDIVIDUAL`, `BULK` or `AUTO` — and never holds `EXPIRATION_POLICY`. The `REVIEWER` / `EXPIRATION_POLICY` / `NONE` wording you see in Campaign History is derived from the revoking actor at read time and is not stored. See [The certification model](/administration-guide/governance/certification/certification-model.md).
{% endhint %}

Two coupling behaviours are easy to trip over. **The screen derives `ALLOW_EXPIRING` from the action:** its help text is *"Leave blank to disable expiration"*, so choosing an action turns expiry and extension on, and clearing the action turns both off; there is no separate checkbox for either. And **save clears the rest when expiry is off** — saving with expiry off nulls `MAX_EXTENSION_EXP_DAYS` and `ON_EXPIRE_ACTION` and turns extension off. Values you entered before turning expiry off are gone, not hidden; reopen the tab after saving and you will see the fields empty.

{% hint style="warning" %}
`ALLOW_EXPIRING` ships `'Y'` but `ON_EXPIRE_ACTION` defaults to `DO_NOTHING`. So out of the box a campaign *can* expire and, when it does, **revokes nothing** — the un-actioned access simply stays in place and the campaign closes as `EXPIRED` or `PARTIALLY_EXPIRED`. If your control asserts that unreviewed access is removed, you must choose `REVOKE_ALL_ACCESS` explicitly, and you must enable `REQUEST_ESCALATION` for the expiry to be detected at all.
{% endhint %}
{% endstep %}

{% step %}

### Enable the two batch tasks, and set two properties

| Task ID              | Name                                 | Cron            | Ships enabled          | Bean and method                                       |
| -------------------- | ------------------------------------ | --------------- | ---------------------- | ----------------------------------------------------- |
| `REQUEST_REMINDERS`  | Notification reminders for approvers | `0 0 0 1/1 * ?` | **no** (`ENABLED='N'`) | `requestRemindersInitializer.initializeSendReminders` |
| `REQUEST_ESCALATION` | Escalation of expired requests       | `0 0 0 1/1 * ?` | **no** (`ENABLED='N'`) | `requestEscalationInitializer.initializeEscalation`   |

Both rows have shipped since 4.2.0.0. Enable them at **Administration → Batch Tasks**; the shipped cron on each is midnight daily. See [Create a batch task](/administration-guide/automation/batch-tasks.md) and [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md).

Then two properties and one field that are not on either tab:

1. Set `org.openiam.idm.activiti.default.approver.user`, so an escalation step that resolves to nobody lands somewhere instead of vanishing.
2. Set `org.openiam.webconsole.defaultBaseDomain`, so escalation mails contain working links.
3. Set **Email Template** on the **Configurations** tab, or the campaign launches with no notification at all.
   {% endstep %}
   {% endstepper %}

## Verification

| Claim                                | Evidence                                                                                                                                           |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Steps persisted with the right order | `CERTIFICATION_REVIEWER` rows for the certification, `REVIEWER_LEVEL` 0..n                                                                         |
| The window is what you intended      | `DAYS_TO_COMPLETE` equals `DAYS_BEFORE_REMINDER × (NUMBER_OF_REMINDERS + 1)` on every row                                                          |
| Reviewers actually resolved          | After launch, `CAMPAIGN_REVIEWERS` joined to `CAMPAIGN_STEP` returns a non-empty set per step; preview `reviewersNotResolvedCount` is 0            |
| Reminders are enabled                | `BATCH_CONFIG` row `REQUEST_REMINDERS` has `ENABLED='Y'`                                                                                           |
| A reminder was sent                  | Audit `SEND_REMINDERS_TO_APPROVERS_PROCEDURE` with a child `SEND_REMINDER_TO_CERTIFICATION_REVIEWER`                                               |
| Escalation is enabled                | `BATCH_CONFIG` row `REQUEST_ESCALATION` has `ENABLED='Y'`                                                                                          |
| An escalation happened               | Audit `ESCALATE_REQUESTS_PROCEDURE` with a child `INITIATE_ESCALATION_OF_EXPIRED_STEP` (or `DELEGATE_TO_DEFAULT_APPROVER` when the step was empty) |
| Expiry revoked as configured         | `ACCESS_REVIEW_ITEM` rows with `ACCESS_REVOKED='Y'` and `ACCESS_REVOKED_BY` = the system user id; audit `INITIATE_CANCELING_OF_EXPIRED_REQUEST`    |

## When it goes wrong

| Symptom                                                                       | Cause                                                                                                                                          | Fix                                                                                                                                            |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| No reminder ever arrives                                                      | `BATCH_CONFIG.ENABLED='N'` on `REQUEST_REMINDERS`                                                                                              | Enable it. Nothing on the Reviewers tab compensates                                                                                            |
| Steps never escalate and items never expire                                   | `BATCH_CONFIG.ENABLED='N'` on `REQUEST_ESCALATION`                                                                                             | Enable it                                                                                                                                      |
| Reminder text edits had no effect                                             | You edited template id `160`, now named `ACCESS_CERTIFICATION_NOTIFY` — it is the one-off notification at campaign execution, not the reminder | Edit `ACCESS_CERT_REMINDER`                                                                                                                    |
| Searching the template list for `ACCESS_CERTIFICATION_REMINDER` finds nothing | Renamed to `ACCESS_CERTIFICATION_NOTIFY` in 4.2.1.7                                                                                            | Search for the new name — [`ACCESS_CERTIFICATION_NOTIFY` and the reviewer's link](#access_certification_notify-and-the-reviewers-link)         |
| Reviewer's mail arrives but the button does not open the campaign             | The link was hand-edited to a `campaignId=` query parameter; the correct form is a path segment                                                | Restore `?frameURL=/selfservice-v2/uar/campaigns/${req.getNotificationParam('CAMPAIGN_ID').valueObj}`                                          |
| Reviewer's mail opens a screen that does not match the guide                  | The shipped link has no `-v2` segment, so it lands on the previous reviewer interface                                                          | Repoint the template path to `/selfservice-v2/uar/campaigns/…`                                                                                 |
| A *reminder* mail carries a short campaign ID                                 | The reminder path recovers the campaign ID by splitting the task name `<certificationId>-<campaignId>` on hyphens                              | A product defect when either identifier contains a hyphen — check both ids, then raise it with support                                         |
| `DAYS_TO_COMPLETE` is not what you typed                                      | It is derived: `DAYS_BEFORE_REMINDER × (NUMBER_OF_REMINDERS + 1)`                                                                              | Change the cadence, not the window                                                                                                             |
| `DAYS_TO_COMPLETE` stuck at `2` on a new step                                 | The derivation only runs when both cadence fields are non-null; `2` is the UI seed                                                             | Set both `DAYS_BEFORE_REMINDER` and `NUMBER_OF_REMINDERS`                                                                                      |
| Preview holds many users back with a `*_NOT_FOUND` reason                     | The reviewer type cannot resolve for those users — no supervisor, no owner, no certifier                                                       | Populate the relationship, or add a later step with a concrete `USER` reviewer                                                                 |
| Escalated task vanished instead of reaching anyone                            | The escalation step resolved to no users and `org.openiam.idm.activiti.default.approver.user` is unset                                         | Set the property; look for audit `DELEGATE_TO_DEFAULT_APPROVER`                                                                                |
| Expired campaign revoked nothing                                              | `ON_EXPIRE_ACTION` is `DO_NOTHING` — the default                                                                                               | Set `REVOKE_ALL_ACCESS` or `REVOKE_ONLY_ALREADY_REVOKED_ACCESS`                                                                                |
| Expired tasks stay open forever                                               | `ALLOW_EXPIRING = 'N'`; the escalation sweep deliberately leaves the task open and audits *"forbids expiring"*                                 | Set expiry on, or close the campaign by hand                                                                                                   |
| Extension rejected                                                            | Validation returned `EMPTY_JUSTIFICATION`, `EXPIRATION_IS_DISABLED`, `EXPIRATION_DAYS_EXCEEDED` or `CAMPAIGN_ALREADY_EXTENDED`                 | Supply a reason; enable expiry; stay within `MAX_EXTENSION_EXP_DAYS` (365 or fewer); a campaign may be extended once — `CAMPAIGN.WAS_EXTENDED` |
| Expiration fields cleared themselves                                          | Save nulls `MAX_EXTENSION_EXP_DAYS` and `ON_EXPIRE_ACTION` whenever `allowExpiring` is false                                                   | Re-enter them with an action selected                                                                                                          |
| A reviewer id column holds the literal `Supervisor`                           | Save normalizes `SUPERVISOR`, `OWNER` and `ADMIN` by writing that literal into `REVIEWER_ENTITY_ID` when you left it blank                     | Expected, not corruption                                                                                                                       |
| A runbook names `ACC_NTFC_REMINDER_BT_`                                       | The task was withdrawn in a later release and is not in **Administration → Batch Tasks**                                                       | Use `REQUEST_REMINDERS`                                                                                                                        |

## Worked configuration

A two-step application review: entitlement owner first with a 14-day window and weekly reminders, application owner second with 7 days, escalating to the compliance group, revoking anything nobody touched.

| Step | Reviewer Type       | `DAYS_BEFORE_REMINDER` | `NUMBER_OF_REMINDERS` | Derived `DAYS_TO_COMPLETE` | Mandatory |
| ---- | ------------------- | ---------------------- | --------------------- | -------------------------- | --------- |
| 0    | `ENTITLEMENT_OWNER` | 7                      | 1                     | 14                         | yes       |
| 1    | `APPLICATION_OWNER` | 7                      | 0                     | 7                          | yes       |

Escalation on step 0: level 0 → the compliance `GROUP`, `DAYS_BEFORE_REMINDER` 3, `NUMBER_OF_REMINDERS` 1 → `DAYS_TO_COMPLETE` 6. Expiration Policy: Action on expire = `REVOKE_ALL_ACCESS`; Extension days = `14`.

## Where reviewer configuration is stored

Reviewer configuration exists in two layers, and knowing which one you are looking at resolves most confusion.

**On the definition** — `CERTIFICATION_REVIEWER`, one row per step:

| Column                 | Field                | Meaning                                               |
| ---------------------- | -------------------- | ----------------------------------------------------- |
| `CERTIFICATION_ID`     | `certification`      | Owning definition; a real in-database FK              |
| `REVIEWER_LEVEL`       | `reviewerLevel`      | Step order, zero-based                                |
| `REVIEWER_ENTITY_TYPE` | `reviewerEntityType` | What kind of reviewer — the values in the table above |
| `REVIEWER_ENTITY_ID`   | `reviewerEntityId`   | The user, role or group id, interpreted per the type  |
| `IS_MANDATORY`         | `mandatory`          | Y/N; **defaults to `true`**                           |
| `DAYS_BEFORE_REMINDER` | `daysBeforeReminder` | Reminder interval, in days                            |
| `NUMBER_OF_REMINDERS`  | `numberOfReminds`    | How many reminders to send                            |
| `DAYS_TO_COMPLETE`     | `daysToComplete`     | The step's window — derived, see above                |
| `MAIL_TEMPLATE_ID`     | `emailTemplateId`    | Template for this step's notifications                |

Its escalation chain is `CERTIFICATION_REVIEWER_ESCALATION`, keyed by `REVIEWER_ID`, with `ESCALATION_LEVEL`, `ESCALATION_ENTITY_TYPE`, `ESCALATION_ENTITY_ID`, `ESCALATION_DISPLAY_NAME` and the same four timing columns.

**On the campaign** — `CAMPAIGN_STEP`, one row per step of one run, holding `STEP_LEVEL`, `REVIEWER_NAME`, `REVIEWER_TYPE`, `TOTAL_COUNT`, `COMPLETED_COUNT` and `ADDITIONAL_INFO`. Its *resolved* human reviewers are `CAMPAIGN_REVIEWERS` rows — two columns, `(STEP_ID, REVIEWER_ID)`, keyed to `CAMPAIGN_STEP.ID`.

{% hint style="info" %}
`CAMPAIGN_REVIEWERS` is the durable record of "who reviewed this campaign". Resolve reviewers from it, not from live Activiti tasks — those disappear when the campaign closes, which would hide a completed campaign from the very people who reviewed it. There is an index on `(REVIEWER_ID)`, `IDX_CAMP_REV_REVIEWER`, added specifically so that "which campaigns did this person review?" stays cheap on a large history.
{% endhint %}

These tables live in the **`certification` database**. Save also stamps `ASSOCIATION_TYPE = CERTIFICATION` on every row. Before the split, the same rows were `APPROVER_ASSOC` with that association type; the rename map is in [Certification database tables](/administration-guide/reference/certification-tables.md#the-legacy-rename-map). The same `DAYS_TO_COMPLETE` derivation governs the classic approver-association screen, so a non-certification approval step behaves identically.

## What the two batch tasks do

### `REQUEST_REMINDERS`

Each run asks Activiti for the requests that are due a reminder, then branches on what kind of request each one is:

* For an **access certification** request it consolidates — one mail per reviewer covering all their outstanding campaign requests — and sends `ACCESS_CERT_REMINDER` with the parameters `REQUEST_ID`, `CAMPAIGN_NAME`, `TO_DATE`, `NUMBER_OF_USERS`, `NUMBER_OF_ITEMS`, `REMINDERS_LEFT`, `CAMPAIGN_ID` and `user`. Because one mail covers several requests, the reminder counter is decremented on every request that mail covered — so a reviewer with five outstanding campaigns can exhaust their reminders in one send.
* For **any other** request type it sends `APPROVER_REMINDER` with `REQUEST_TYPE`, `REQUEST_ID` and `user`.

Audit: the sweep itself is `SEND_REMINDERS_TO_APPROVERS_PROCEDURE`; each child event is `SEND_REMINDER_TO_CERTIFICATION_REVIEWER` for a campaign, `SEND_REMINDER_TO_APPROVER` otherwise.

### `REQUEST_ESCALATION`

Each run finds every workflow task whose due date has passed and, per task:

1. **If a further escalation level is configured**, it escalates — audit `INITIATE_ESCALATION_OF_EXPIRED_STEP` — and collects the notification contexts. Escalation mails are then **consolidated per recipient** and sent as `ESC_UAR_TASK_CONSOLIDATED` with `ESCALATION_TASK_COUNT`, the campaigns and contexts, and a `baseUrl` taken from `org.openiam.webconsole.defaultBaseDomain`. Leave that property unset and the mail still goes out, with links that go nowhere.
2. **If the escalation step resolves to no users**, it delegates to the default approver from `org.openiam.idm.activiti.default.approver.user` with the comment *"This task was delegated due to problem of calculating of the escalate step. Please take an action"* — audit `DELEGATE_TO_DEFAULT_APPROVER`. **Set that property**, or an escalation with an empty target goes nowhere.
3. **If no escalation level is left**, behaviour depends on the request type. For an access certification it consults the certification's `ALLOW_EXPIRING` setting: when expiry is allowed the task is cancelled with *"Canceled by system due to expired period of approval"* and status `CANCELED_AS_EXPIRED`; when it is not, the task **stays open** and the audit records *"Configuration of access certification campaign \[name] forbids expiring. Task will remain open."* Either way the audit action is `INITIATE_CANCELING_OF_EXPIRED_REQUEST`, so read the message to tell the two apart.

Audit for the sweep: `ESCALATE_REQUESTS_PROCEDURE`.

### Withdrawn tasks a stale runbook may name

Four certification-related batch task IDs were seeded by an earlier release and **removed by a later one**. They do not exist in a current install, and you will not find them under **Administration → Batch Tasks**: `ACC_NTFC_REMINDER_BT_`, `ACC_CERT_GEN_REPORT`, `ATTESTATION`, `GROUP_ATTESTATION` (and `CANCEL_UAR_CAMPAIGN_REQ`). The live pair is `REQUEST_REMINDERS` and `REQUEST_ESCALATION`.

## The reminder and escalation mail templates

Every template below exists in the shipped `MAIL_TEMPLATE` inventory ([Shipped mail templates](/administration-guide/reference/shipped-mail-templates.md)); confirm any of them under **Administration → Mail Template Editor** by its ID.

| Template ID                              | Sent by                                  | Use                                                                                                                                                                 |
| ---------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ACCESS_CERT_REMINDER`                   | The `REQUEST_REMINDERS` batch task       | **The certification reviewer reminder.** This is the one to customise                                                                                               |
| `APPROVER_REMINDER` (id `158`)           | The `REQUEST_REMINDERS` batch task       | Reminder for non-certification approval requests                                                                                                                    |
| `TASK_ASSIGNEE_REMINDER` (id `137`)      | The `TASK_EMAIL_NOTIFICATION` batch task | Generic task-assignee nudge; not the certification path                                                                                                             |
| `ESC_UAR_TASK_CONSOLIDATED`              | The `REQUEST_ESCALATION` batch task      | Consolidated certification escalation mail                                                                                                                          |
| `ESCALATE_TASK_ACCESS_CERT_TASK`         | The certification escalation path        | Per-task certification escalation                                                                                                                                   |
| `ESCALATE_TASK` (id `156`)               | The generic escalation path              | Non-certification escalation                                                                                                                                        |
| `UAR_DELEGATED_TASK`                     | The certification escalation path        | A certification task was delegated                                                                                                                                  |
| `ACCESS_CERTIFICATION_NOTIFY` (id `160`) | Campaign execution, once per reviewer    | **Not a reminder.** The reviewer's initial "a campaign has been assigned to you" mail. Shipped as `ACCESS_CERTIFICATION_REMINDER` in 4.2.0.0 and renamed in 4.2.1.7 |

{% hint style="warning" %}
**Three names, one letter of difference, and only one of them is the reminder.** `ACCESS_CERTIFICATION_NOTIFY` (template id `160`) fires **once**, when the campaign is executed. `ACCESS_CERT_REMINDER` is the one the `REQUEST_REMINDERS` batch task sends repeatedly while the campaign is open. `ACCESS_CERTIFICATION_APPROVED` / `ACCESS_CERTIFICATION_REJECT` are decision outcomes. Editing id `160` and expecting the reminder wording to change is the single most common miss here — and if you are searching for `ACCESS_CERTIFICATION_REMINDER`, that name no longer exists in any release from 4.2.1.7 onward. All four are on **Administration → Mail Template Editor**; open them side by side once and the distinction stops being confusing.
{% endhint %}

### `ACCESS_CERTIFICATION_NOTIFY` and the reviewer's link

The mail goes out once per reviewer, on the campaign-execution path. The template it uses is the one the campaign names in **Email Template** on its **Configurations** tab — so if that field is empty **no mail is sent at all**, and nothing tells you. That is why step 5 above insists on it, and why a launch with `INITIATE_CERTIFICATION_WORKFLOW` in the audit log but no reviewer complaints is worth checking rather than celebrating.

Four notification parameters reach the body, plus the recipient:

| Parameter       | Value                                                                               |
| --------------- | ----------------------------------------------------------------------------------- |
| `CAMPAIGN_NAME` | The certification's name                                                            |
| `CAMPAIGN_ID`   | The campaign identifier, passed whole and unaltered                                 |
| `TO_DATE`       | Deadline as `MM/dd/yyyy`, or the literal `anytime` when no completion window is set |
| `NUMBER_OF`     | How many users *this* reviewer must certify                                         |
| `user`          | The recipient, so `${user.getDisplayName()}` resolves                               |

The link the reviewer needs is a **path segment**, not a query parameter. The shipped body is:

{% code overflow="wrap" %}

```html
<a class="button" href="http://localhost.openiam.com/selfservice/?frameURL=/selfservice/uar/campaigns/${req.getNotificationParam('CAMPAIGN_ID').valueObj}">Link</a>
```

{% endcode %}

Keep the campaign id as a path segment. A mail whose button carries `campaignId=` instead is a hand-edited template, and the campaign will not open.

{% hint style="warning" %}
**As shipped, that link opens the previous reviewer screen.** The path in the template has no `-v2` segment, and `/selfservice/uar/campaigns/…` serves the older interface — not the one documented in [What your reviewers see](/administration-guide/governance/certification/the-reviewer-experience.md). Nothing since 2026.6.1.0 has repointed it, and the SelfService v2 menu rows carry the same older path.

If your reviewers work in the current interface, change the host **and** the path when you adapt this template, to `/selfservice-v2/uar/campaigns/<CAMPAIGN_ID>`. Send one test mail and click the button before a real launch — the two screens are easy to tell apart, and the current one is headed **Certifications**.
{% endhint %}

{% hint style="info" %}
**A reported truncated campaign ID — verdict: not the initial notification, but the mechanism is real one path over.** A community report describes the campaign link rendering as `campaignId=nBUGE` instead of the full 32-character identifier, followed by a `NullPointerException` and a reviewer who cannot open the task. The report attributes it to writing `.getValueObj()` in the template instead of `.valueObj`.

The accessor swap is folklore: the two forms are equivalent, the parameter has no length handling, and `CAMPAIGN_ID` reaches `ACCESS_CERTIFICATION_NOTIFY` whole. Changing the template between those two forms will not change what the mail renders. We could not reproduce the truncation on the initial notification.

**What is real** is that the *reminder* path recovers the campaign ID by string surgery rather than being handed it. The reviewer's workflow task is named `<certificationId>-<campaignId>`, and the `ACCESS_CERT_REMINDER` path splits that name on hyphens and takes the second piece. That is correct **only while neither identifier contains a hyphen**. Give either one a hyphen — an imported campaign, a custom ID generator — and the fragment resolves to no campaign, and the reminder fails.

So: if the *initial* notification carries a short campaign ID, the template is not the cause and you should raise it with support with the rendered mail attached. If a *reminder* carries one, check your `CERTIFICATION.ID` and `CAMPAIGN.ID` values for hyphens first — that is a product defect worth reporting, and there is no configuration that works around it.
{% endhint %}

Campaign lifecycle mails, for completeness: `UAR_CAMPAIGN_COMPLETED`, `UAR_CAMPAIGN_EXPIRED`, `UAR_REVIEWER_CAMPAIGN_COMPLETE`, `UAR_CAMPAIGN_DELETED` and `CERT_COMPLETE_REPORT`.

## Bulk approval

Not a reviewer-step field, but decided in the same conversation. `ALLOW_BULK_APPROVAL`, which ships `'N'`, lets a reviewer approve or revoke every item for a user or entitlement in one click. It is copied onto `CAMPAIGN.ALLOW_BULK_APPROVAL` at launch, so a running campaign keeps the value it launched with.

The product's own disclaimer is worth quoting because it is the reason to leave it off: *"Bulk approval is risky - enable at your own risk. … This undermines the purpose of an access certification campaign, where each grant is meant to be reviewed on its own merits, and makes it easy to rubber-stamp excessive or inappropriate access, weakening your audit trail and compliance evidence."* Items decided this way are marked — `ACCESS_REVIEW_ITEM_AUDIT.DECISION_SOURCE = 'BULK'`, and the complete report carries a `BULK_APPROVAL_RISK` column reading *"YES - reviewed via bulk approval (RISKY: items not reviewed individually)"*. Operating it is on [Operate a live campaign](/administration-guide/governance/certification/operate-a-live-campaign.md).

## Related

* [Plan and scope a campaign](/administration-guide/governance/certification/plan-and-scope-a-campaign.md) — where reviewer resolution is checked before you commit
* [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md) — every task, cron and shipped enabled state
* [Create a batch task](/administration-guide/automation/batch-tasks.md) — how to enable and run one
* [Shipped mail templates](/administration-guide/reference/shipped-mail-templates.md) — the full template inventory
* [Operate a live campaign](/administration-guide/governance/certification/operate-a-live-campaign.md) — extending, expiring and bulk decisions in a live campaign

*Applies to OpenIAM 2026.9.1. Last reviewed 14 September 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/governance/certification/configure-the-review-workflow.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
