Confirm approval decisions with a one-time code sent by email
Description
Problem
A decision is recorded as soon as someone acting as the approver clicks a decision button: in the panel, in an email, in Slack or Teams, or through Rovo. Anyone using the approver's open Jira or Confluence session, a forwarded email with action buttons, or the approver's chat account can decide in their name. Organizations that must show the approver decided themselves have no way to require a confirmation.
Change
Settings
Global, on the "Settings" tab of the admin page "Decision confirmation", for global admins, in Jira and Confluence:
-
Method: Disabled (default), PIN (AP-147), One-time code (this ticket), Authenticator app (AP-730). One method per site, read whenever someone decides, so a change reaches running approvals at once.
-
Scope, shown while a method is chosen: Enabled (on by default in every definition, a definition can turn it off) or Required (every definition, none can turn it off).
Definition, new checkbox "Confirm decisions" (New badge), shown while a method is chosen:
-
Checked by default for new and existing definitions. Under Required it is checked and disabled, with "Required in Decision confirmation > Settings" in its tooltip.
-
Saved only while a method is chosen and the scope is Enabled; otherwise the stored value is kept and applies again once the scope is Enabled. Duplicating a definition copies it.
-
No per-step option and no option on the Parametrize form.
A decision needs a code when the method is One-time code, the scope is Required or the approval's definition had "Confirm decisions" on when the approval started, and people decide the step (not notification, automation, webhook or AI Prompt steps). Reruns read the definition again; approvals started before this ticket count as on. Email approvers get this code under every method, as they have no account for a PIN or an app.
Confirming a decision
While the method is One-time code, every person who decides confirms with a code: approvers, delegates, group members, voters, vote moderators, email approvers and JSM customers in the portal, for Approve, Consent, Reject, Abstain and Vote. Steps no one decides and admin actions (start, rerun, reassign, archive, delete) need no code.
-
After the decision button, and the comment when one is required, the app emails a 6-digit code and asks for it. The decision is recorded only with the right code. A decision that is refused anyway (the approval is completed, archived, incomplete, outdated or past its deadline, or a required comment is missing) is refused before the code is checked, so it costs no attempt.
-
A code works once, for 10 minutes, and only for the person, approvals, steps and action it was sent for, in the place it was requested from.
-
5 wrong entries end a code. A new code can be requested after 60 seconds, or at once after the previous code confirmed a decision, and replaces the previous one once it is sent; asking again within 60 seconds for the same decision reuses the code already sent. At most 10 codes per hour; codes that confirmed a decision do not count. After 20 wrong entries within 24 hours, codes are paused (no new codes, and codes already sent are refused) until fewer than 20 remain in the last 24 hours.
-
Codes and these limits count per person and per place the code was requested from (the app, the email links of a step, the Web API step links of a step, Slack, Teams, the approval page for email approvers), so a forwarded email cannot block the approver elsewhere. Across all email links that open without login, and separately across the person's other places (the app, Slack, the approval page, and links that need a login), codes are paused after 100 wrong entries within 24 hours, and at most 30 codes are sent per hour.
-
One code confirms one decision. A bulk decision needs one code for all selected approvals that need one. A bulk code keeps working, until it expires, for its approvals that were not yet decided with it; each approval only once.
How the code is sent:
|
Who decides |
The code is sent |
|---|---|
|
Jira user, notification mechanism Direct email |
by the app to the account's email address; as a Jira notification email when the address is unknown |
|
Jira user, other mechanisms (Jira notification, Slack, Teams) |
as a Jira notification email |
|
Confluence user, any mechanism |
by the app to the account's email address |
|
Email approver |
by the app to the step's email address |
The email names the action (its button name), the approval, the step, the work item key or page title, and that the code expires after 10 minutes. It tells the person what to do if they did not try to decide. For a bulk decision it names how many approvals the code covers and up to 10 of them. The text is fixed, not a message template; the custom HTML wrapper of Settings > Email applies. A custom wrapper without <section id="content"></section> is now ignored and the default wrapper is used, so no email, also outside this feature, goes out without its content. The app never shows the address: "We emailed a code to your account's email address." (Slack: "We emailed a code to your email address.")
When the code cannot be sent (Confluence cannot read the address, outgoing mail turned off on the Jira site, a mail server error) the person sees a short reason without technical details, nothing is recorded, and admins get an instance error with the cause. Neither shows an email address.
Where the code is asked
-
Panel (Jira work item, JSM customer portal, Confluence page, approvals list details): the "Confirm your decision" dialog opens after the decision button. It shows what is being confirmed, sends the code and offers the code field, "Send a new code" after 60 seconds, Confirm and Cancel; a wrong or expired code shows in the dialog. Decisions started from list row actions, the Confluence home feed, and email or Slack links that open the work item or page ask in the same dialog instead of being recorded at once. When a code turns out to be needed only on submit (for example the setting changed meanwhile), the decision is refused and the dialog opens.
-
Bulk decisions (approvals list and panel): the bulk dialog asks for the code after the comments. One code covers one kind of decision. Approvals found to need a code only on submit are left undecided and a second code dialog asks for them; when it is cancelled, the flag says how many approvals were not decided.
-
Email action buttons and Web API step links: the page they open asks for the code. It sends a code only when "Send code" is clicked, never when the page opens, because mail scanners open links. Decisions that skip this page today (logged-in users, no comment needed) show it when a code is needed.
-
Approval page for email approvers: the decision form gets "Send code" and the code field. A wrong code keeps the form and the comment.
-
Slack: the decision buttons open the dialog with the code field, and the comment field when there is one. The code is emailed when the dialog opens. Wrong codes and "Send a new code" are in the dialog.
-
Teams: the same in the decision dialog. One-click buttons on approvals that need a code open the dialog; a card sent before the setting changed refuses a one-click decision and offers the dialog.
-
Rovo: refuses decisions on approvals that need a code and gives the link to the work item or page. Its lists say which approvals need a code and point to the work item or page, without preparing a decision. Rovo never asks for a code, because anything typed in the chat goes through the language model.
-
Web API: refuses decisions on approvals that need a code (reason
CODE_CONFIRMATION_REQUIRED), unless the new global option "Skip decision confirmation for Web API decisions" is on (off by default). The option is in Settings > General > Web API under "Approval decisions through the Web API", shown while that toggle is on and a method is chosen.
Activity
-
A decision confirmed with a code shows a lock icon with "Confirmed with a one-time code" in the panel's step details, also for each group member and voter. A Web API decision recorded under the skip option shows an open lock with "One-time code confirmation skipped". An email approver's code under the other methods shows "Confirmed with a one-time code instead of a PIN" or "... instead of an authenticator app". Decisions that needed no confirmation show nothing.
-
Activity shows the same on the step's row. A group or vote step's row shows the skipped mark when any member's decision skipped it, otherwise the confirmed one.
-
The Activity CSV export gets a "Confirmation" column, shared by every method; for codes: One-time code, One-time code skipped, One-time code instead of PIN, One-time code instead of authenticator app, or empty. AP-730's confirmation log also records confirmed decisions and wrong codes.
Decisions
-
Codes go only by email, never by Slack or Teams, also when chat is the notification mechanism: the code must reach the person outside the place where they decide. Confluence comment mentions are visible to everyone who can see the page, so Confluence always uses the app's email.
-
One code per decision request. There is no "trusted for some minutes" period after a confirmed code.
-
The rule is evaluated when someone decides, from the current global setting and what the approval started with, so a global change reaches running approvals at once. One method per site: a decision needs exactly one proof.
-
Codes are kept only as a keyed hash and never logged. Code emails do not go through the notification queue: they are sent while the person waits, so errors show at once. The app's stored copy of the email has the code masked.
-
The feature confirms that the person can read the approver's email at decision time. It is not multi-factor authentication: NIST SP 800-63B-4 does not accept email as an authenticator.
-
The email address is never shown, following Atlassian's rules for apps that read user email addresses.
Out of scope
-
Codes by SMS or chat messages; a setting per step or per approval; an admin-editable code email.
-
Web API definition endpoints: no new field. Definitions created through them get "Confirm decisions" on; updates keep the stored value. Options sent when starting an approval through the Web API: AP-2488.
-
Webhook payloads, the Approval Status field, JQL and Web API responses other than the new refusal.
Acceptance criteria
-
Method Disabled: no channel asks for a code, and the scope and the definition checkbox are hidden; saving a definition keeps its stored choice.
-
Method One-time code, scope Enabled, definition checked: every channel listed above asks for a code; Rovo and the Web API refuse. A wrong code is refused with the attempts left; the right code records the decision, and Activity shows "Confirmed with a one-time code". A definition with "Confirm decisions" cleared asks for no code, also on reruns.
-
Scope Required: every approval needs a code, also those whose definition has it cleared, and the checkbox is locked on. Switching back to Enabled restores each definition's choice.
-
Method PIN or Authenticator app: email approvers still confirm with a code, marked "instead of".
-
A code is refused when used twice, after 10 minutes, after 5 wrong entries, after a newer code was sent, by another person, from another place, for another action, or for an approval or step it was not sent for. Code requests stop at the limits above.
-
Jira: the code arrives as a Jira notification email under the Jira notification, Slack and Teams mechanisms, and from the app under Direct email. Confluence users and email approvers get it from the app.
-
A bulk decision of 25 approvals, some needing a code, asks for one code and records all of them.
-
Opening an email action link sends nothing. A decision that needs a code is never recorded without one, also for logged-in users.
-
Slack and Teams: a decision with a code works in the dialog; a wrong code shows in the dialog.
-
Rovo refuses with a link. The Web API refuses with
CODE_CONFIRMATION_REQUIRED, or, with the skip option on, records the decision and Activity shows "One-time code confirmation skipped". -
Only global admins change the global settings. A crafted definition save cannot turn confirmation off while the scope is Required. A code request for an approval or step the person cannot decide on is refused and sends nothing; in a bulk request such approvals are left out, and the request is refused when none is left.
-
No code appears in logs, the notification queue or the app's stored emails.
Manual tester notes
-
Jira notification delivery: time from the request to the inbox; delivery to a user without Browse permission on the project and to a user whose email is hidden in the profile; a site with outgoing mail turned off (expect the "outgoing email is turned off" message and an instance error).
-
JSM customer with portal-only access (no Jira license), under the Jira notification and Direct email mechanisms: the code email arrives in the customer's inbox. Check the inbox, not the dialog: Jira answers success even when it drops a recipient. (Jira notifications reached a portal-only customer on wd-test-1, 2026-10-28, on a request they reported and on one they are not part of.)
-
App email: Gmail, Outlook and Apple Mail, also on a phone; each carrier (integrated, SMTP, Gmail, Outlook); the stored email copy shows the code masked; a custom HTML wrapper with and without the content placeholder (code and other emails keep their content).
-
A code requested on the email action page or in Slack, entered in the panel, is refused.
-
Confluence: decide from a page, the byline dialog and the home feed; a user whose email is hidden in the profile.
-
Email action page: open the link through Outlook Safe Links or another link scanner; no code email arrives until "Send code" is clicked.
-
Email approvers: the approval page after logging in by password and by login link; a wrong code keeps the comment.
-
Slack: dialog with and without a comment, wrong code, "Send a new code", an email step, Slack on a phone.
-
Teams: dialog on desktop, web and phone; a card sent before the setting was switched on.
-
Rovo: single and batch decisions on approvals that need a code; the Jira texts never name Confluence and the Confluence texts never name Jira.
-
Web API: the refusal, then the skip option; Activity and the CSV "Confirmation" column.
-
Settings: each method with scope Enabled (definition on and off) and Required; email approvers under PIN and Authenticator app get a code on the approver portal, email links and Slack.
-
Two backend nodes: request a code on one node and confirm on the other; the limits hold across nodes.
-
Delegates (also "decide as" in the Slack dialog), group steps, vote steps (a vote and the moderator), parallel groups, a step reassigned after the email with action buttons was sent (its link no longer gets a code).
-
Two browser tabs on the same approval: a code requested in one tab after 60 seconds replaces the code shown in the other; a forwarded email link used by someone else does not replace or pause the approver's codes in the panel.
-
A Jira notification for a user Jira does not email (notifications off, no access): the "No email?" hint, which the dialog and the email pages show 60 seconds after any code was sent.