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, new section "Decision confirmation" in Settings > Actions, for global admins, in Jira and Confluence:

  • Disabled (default): decisions need no code. Definitions and approvals cannot turn codes on.

  • Enabled: decisions need a code. Definitions and approvals can turn it off.

  • Required: every decision needs a code. Definitions and approvals cannot turn it off.

Definition, new option "Confirm decisions with a code":

  • Enabled (default for new and existing definitions): decisions on its approvals need a code; the starter can turn it off on "Start with parameters".

  • Required: decisions need a code; the starter cannot turn it off.

  • Disabled: no code.

  • While the global setting is Disabled the option shows "Turned off in Settings > Actions" and cannot be changed; while it is Required, "Required in Settings > Actions". Saving the definition keeps its own value, which applies again once the global setting is Enabled.

Approval, new checkbox "Confirm decisions with a code" on "Start with parameters", for starters with the Parametrize permission:

  • Shown while a code applies: global Enabled with the definition Enabled or Required, or global Required. Checked by default.

  • Can be cleared only when the global setting and the definition are both Enabled. Otherwise it is checked and disabled with "Required in Settings > Actions" or "Required by the definition".

  • Reruns and relaunch keep a choice changed on "Start with parameters", like the other options of AP-2487. A running approval keeps what it started with.

Rule applied whenever someone decides:

  • global Disabled: no approval needs a code;

  • global Required: every approval needs a code;

  • global Enabled: an approval needs a code unless its definition was Disabled when it started or the starter turned it off.

Approvals started before this ticket count as started with the definition's default (Enabled). Switching the global setting to Enabled therefore asks for codes on running approvals too.

Confirming a decision

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 code works once, for 10 minutes, and only for the person, approvals, steps and action it was sent for.

  • 5 wrong entries end a code. A new code can be requested after 60 seconds and replaces the previous one; asking again within 60 seconds for the same decision reuses the code already sent. At most 10 codes per hour. After 20 wrong entries within 24 hours, codes are paused 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, each email link, Slack, Teams, the approval page for email approvers), so a forwarded email link cannot block the approver elsewhere.

  • One code confirms one decision. A bulk decision needs one code for all selected approvals that need one.

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 app never shows the address: "We emailed a code to your account's email address."

When the code cannot be sent (no readable 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): a dialog opens after the decision button. It 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.

  • Bulk decisions (approvals list and panel): the bulk dialog asks for the code after the comments.

  • Email action buttons: 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. 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 code 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 decision confirmation is Enabled or Required.

Activity

  • A decision confirmed with a code shows "Confirmed with a code" in the panel's step details, also for each group member and voter. A Web API decision recorded under the skip option shows "Code confirmation skipped". Decisions that needed no code show nothing new.

  • Activity shows the same on the step's row. A group or vote step's row shows "Code confirmation skipped" when any member's decision skipped it, otherwise "Confirmed with a code" when any was confirmed.

  • The Activity CSV export gets a "Code confirmation" column with the row's value: Confirmed, Skipped or empty.

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.

  • 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, authenticator apps or chat messages; a setting per step; an admin-editable code email.

  • Web API definition endpoints: no new field. Definitions created through them get Enabled; 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

  • Global Disabled: no channel asks for a code. The definition option is locked with "Turned off in Settings > Actions"; "Start with parameters" does not show the checkbox.

  • Global Enabled, definition Enabled: every channel listed above asks for a code. A wrong code is refused with the attempts left; the right code records the decision, and Activity shows "Confirmed with a code". An approval started with the checkbox cleared asks for no code, and its reruns keep that.

  • Definition Required: the checkbox is checked and disabled. Definition Disabled: no codes while the global setting is Enabled, codes while it is Required.

  • Global Required: every approval needs a code, also those started with the checkbox cleared. Switching back to Enabled restores what each approval started with.

  • A code is refused when used twice, after 10 minutes, after 5 wrong entries, after a newer code was sent, by another person, 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 "Code confirmation skipped".

  • Only global admins change the global settings. A crafted start request cannot turn the code off where the definition or the global setting does not allow it. A code request for an approval or step the person cannot decide on is refused and sends nothing.

  • 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 JSM customer, 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).

  • 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.

  • 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 export.

  • 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 dialog's "No email?" hint after 60 seconds.