Skip to content

Flexible schema support with default fallback in Email Executor#2461

Merged
ThaminduDilshan merged 1 commit intoasgardeo:mainfrom
RandithaK:feature/flex-schema-email-executor
Apr 28, 2026
Merged

Flexible schema support with default fallback in Email Executor#2461
ThaminduDilshan merged 1 commit intoasgardeo:mainfrom
RandithaK:feature/flex-schema-email-executor

Conversation

@RandithaK
Copy link
Copy Markdown
Contributor

@RandithaK RandithaK commented Apr 27, 2026

Purpose

Added capability to resolve and use a flexible name for email, in the user schema.
Example Use Case,
In case the usertype has workemail instead of email.

Approach

Resolve the email address containing field from the relevant flows json file.

Tested with the following file, as user onboarding json, with a modified user schema
   {
    "name": "User Onboarding Flow",
    "handle": "default-user-onboarding",
    "flowType": "USER_ONBOARDING",
    "nodes": [
        {
            "id": "start",
            "type": "START",
            "onSuccess": "permission_validator"
        },
        {
            "id": "permission_validator",
            "type": "TASK_EXECUTION",
            "properties": {
                "requiredScopes": ["system"]
            },
            "executor": {
                "name": "PermissionValidator"
            },
            "onSuccess": "user_type_resolver"
        },
        {
            "id": "user_type_resolver",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "UserTypeResolver"
            },
            "onSuccess": "ou_selection",
            "onIncomplete": "prompt_usertype"
        },
        {
            "id": "prompt_usertype",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "heading_usertype",
                        "label": "{{ t(onboarding:forms.user_type.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "type": "TEXT",
                        "id": "subtitle_usertype",
                        "label": "{{ t(onboarding:forms.user_type.subtitle) }}"
                    },
                    {
                        "type": "BLOCK",
                        "id": "block_usertype",
                        "components": [
                            {
                                "type": "SELECT",
                                "id": "usertype_input",
                                "ref": "userType",
                                "label": "{{ t(onboarding:forms.user_type.fields.user_type.label) }}",
                                "placeholder": "{{ t(onboarding:forms.user_type.fields.user_type.placeholder) }}",
                                "required": true,
                                "options": []
                            },
                            {
                                "type": "ACTION",
                                "id": "action_usertype",
                                "label": "{{ t(onboarding:forms.user_type.actions.continue.label) }}",
                                "variant": "PRIMARY",
                                "eventType": "SUBMIT"
                            }
                        ]
                    }
                ]
            },
            "prompts": [
                {
                    "inputs": [
                        {
                            "ref": "usertype_input",
                            "identifier": "userType",
                            "type": "SELECT",
                            "required": true
                        }
                    ],
                    "action": {
                        "ref": "action_usertype",
                        "nextNode": "user_type_resolver"
                    }
                }
            ]
        },
        {
            "id": "ou_selection",
            "type": "TASK_EXECUTION",
            "properties": {
                "resolveFrom": "prompt"
            },
            "executor": {
                "name": "OUResolverExecutor"
            },
            "onSuccess": "prompt_email",
            "onIncomplete": "prompt_ou_selection"
        },
        {
            "id": "prompt_ou_selection",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "heading_ou_selection",
                        "label": "{{ t(onboarding:forms.ou_selection.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "type": "TEXT",
                        "id": "subtitle_ou_selection",
                        "label": "{{ t(onboarding:forms.ou_selection.subtitle) }}"
                    },
                    {
                        "type": "BLOCK",
                        "id": "block_ou_selection",
                        "components": [
                            {
                                "type": "OU_SELECT",
                                "id": "ou_selection_input",
                                "ref": "ouId",
                                "label": "{{ t(onboarding:forms.ou_selection.fields.ou.label) }}",
                                "required": true
                            },
                            {
                                "type": "ACTION",
                                "id": "action_ou_selection",
                                "label": "{{ t(onboarding:forms.ou_selection.actions.continue.label) }}",
                                "variant": "PRIMARY",
                                "eventType": "SUBMIT"
                            }
                        ]
                    }
                ]
            },
            "prompts": [
                {
                    "inputs": [
                        {
                            "ref": "ou_selection_input",
                            "identifier": "ouId",
                            "type": "OU_SELECT",
                            "required": true
                        }
                    ],
                    "action": {
                        "ref": "action_ou_selection",
                        "nextNode": "ou_selection"
                    }
                }
            ]
        },
        {
            "id": "prompt_email",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "text_header_email",
                        "label": "{{ t(onboarding:forms.email.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "type": "BLOCK",
                        "id": "block_email",
                        "components": [
                            {
                                "id": "input_prompt_email",
                                "ref": "workemail",
                                "type": "EMAIL_INPUT",
                                "label": "{{ t(onboarding:forms.email.fields.email.label) }}",
                                "required": true,
                                "placeholder": "{{ t(onboarding:forms.email.fields.email.placeholder) }}"
                            },
                            {
                                "type": "ACTION",
                                "id": "action_submit_email",
                                "label": "{{ t(onboarding:forms.email.actions.next.label) }}",
                                "variant": "PRIMARY",
                                "eventType": "SUBMIT"
                            }
                        ]
                    }
                ]
            },
            "prompts": [
                {
                    "inputs": [
                        {
                            "ref": "input_prompt_email",
                            "identifier": "workemail",
                            "type": "EMAIL_INPUT",
                            "required": true
                        }
                    ],
                    "action": {
                        "ref": "action_submit_email",
                        "nextNode": "check_email_uniqueness"
                    }
                }
            ]
        },
        {
            "id": "check_email_uniqueness",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "AttributeUniquenessValidator"
            },
            "onSuccess": "prompt_invite_method",
            "onIncomplete": "prompt_email"
        },
        {
            "id": "prompt_invite_method",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "type": "TEXT",
                        "id": "text_header_invite_method",
                        "label": "{{ t(onboarding:forms.invite_mode.title) }}",
                        "variant": "HEADING_1",
                        "align": "center"
                    },
                    {
                        "type": "TEXT",
                        "id": "text_subtitle_invite_method",
                        "label": "{{ t(onboarding:forms.invite_mode.subtitle) }}"
                    },
                    {
                        "type": "BLOCK",
                        "id": "block_invite_method_actions",
                        "components": [
                            {
                                "type": "STACK",
                                "id": "stack_invite_actions",
                                "direction": "row",
                                "justify": "center",
                                "components": [
                                    {
                                        "type": "ACTION",
                                        "id": "action_share_manually",
                                        "label": "{{ t(onboarding:forms.invite_mode.actions.link.label) }}",
                                        "variant": "OUTLINED",
                                        "eventType": "SUBMIT"
                                    },
                                    {
                                        "type": "ACTION",
                                        "id": "action_send_email_invite",
                                        "label": "{{ t(onboarding:forms.invite_mode.actions.email.label) }}",
                                        "variant": "PRIMARY",
                                        "eventType": "SUBMIT"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            },
            "prompts": [
                {
                    "action": {
                        "ref": "action_send_email_invite",
                        "nextNode": "invite_generate_email"
                    }
                },
                {
                    "action": {
                        "ref": "action_share_manually",
                        "nextNode": "invite_generate_manual"
                    }
                }
            ]
        },
        {
            "id": "invite_generate_manual",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "InviteExecutor",
                "mode": "generate"
            },
            "onSuccess": "invite_link_status"
        },
        {
            "id": "invite_generate_email",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "InviteExecutor",
                "mode": "generate"
            },
            "onSuccess": "send_invite_email"
        },
        {
            "id": "send_invite_email",
            "type": "TASK_EXECUTION",
            "properties": {
                "emailTemplate": "USER_INVITE"
            },
            "executor": {
                "name": "EmailExecutor",
                "mode": "send",
                "inputs": [
                    {
                        "ref": "input_workemail",
                        "identifier": "workemail",
                        "type": "EMAIL_INPUT",
                        "required": true
                    }
                ]
            },
            "onSuccess": "email_invite_status",
            "onFailure": "invite_email_service_unavailable"
        },
        {
            "id": "email_invite_status",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "email_status_icon",
                        "label": "",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "email_status_heading",
                        "label": "{{ t(onboarding:forms.invite_email_sent.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "email_status_message",
                        "label": "{{ t(onboarding:forms.invite_email_sent.message) }}"
                    }
                ]
            },
            "message": "{{ t(onboarding:forms.invite_email_sent.title) }}",
            "next": "invite_verify"
        },
        {
            "id": "invite_email_service_unavailable",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "email_unavailable_icon",
                        "label": "⚠️",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "email_unavailable_heading",
                        "label": "{{ t(onboarding:forms.invite_email_unavailable.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "email_unavailable_message",
                        "label": "{{ t(onboarding:forms.invite_email_unavailable.message) }}"
                    },
                    {
                        "type": "COPYABLE_TEXT",
                        "id": "email_unavailable_link_copyable",
                        "label": "{{ t(onboarding:forms.invite_link_status.link_label) }}",
                        "source": "inviteLink"
                    }
                ]
            },
            "message": "{{ t(onboarding:forms.invite_email_unavailable.title) }}",
            "next": "invite_link_status"
        },
        {
            "id": "invite_link_status",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "link_status_icon",
                        "label": "",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "link_status_heading",
                        "label": "{{ t(onboarding:forms.invite_link_status.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "link_status_message",
                        "label": "{{ t(onboarding:forms.invite_link_status.message) }}"
                    },
                    {
                        "type": "COPYABLE_TEXT",
                        "id": "invite_link_copyable",
                        "label": "{{ t(onboarding:forms.invite_link_status.link_label) }}",
                        "source": "inviteLink"
                    }
                ]
            },
            "message": "The invite link is ready to share",
            "next": "invite_verify"
        },
        {
            "id": "invite_verify",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "InviteExecutor",
                "mode": "verify",
                "inputs": [
                    {
                        "ref": "input_003",
                        "identifier": "inviteToken",
                        "type": "HIDDEN",
                        "required": true
                    }
                ]
            },
            "onSuccess": "prompt_user_details"
        },
        {
            "id": "prompt_user_details",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "text_header_details",
                        "label": "{{ t(onboarding:forms.user_details.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "type": "BLOCK",
                        "id": "block_user_details",
                        "components": [
                            {
                                "id": "input_prompt_username",
                                "ref": "username",
                                "type": "TEXT_INPUT",
                                "label": "{{ t(onboarding:forms.user_details.fields.username.label) }}",
                                "required": true,
                                "placeholder": "{{ t(onboarding:forms.user_details.fields.username.placeholder) }}"
                            },
                            {
                                "id": "input_prompt_given_name",
                                "ref": "given_name",
                                "type": "TEXT_INPUT",
                                "label": "{{ t(onboarding:forms.user_details.fields.first_name.label) }}",
                                "required": false,
                                "placeholder": "{{ t(onboarding:forms.user_details.fields.first_name.placeholder) }}"
                            },
                            {
                                "id": "input_prompt_family_name",
                                "ref": "family_name",
                                "type": "TEXT_INPUT",
                                "label": "{{ t(onboarding:forms.user_details.fields.last_name.label) }}",
                                "required": false,
                                "placeholder": "{{ t(onboarding:forms.user_details.fields.last_name.placeholder) }}"
                            },
                            {
                                "type": "ACTION",
                                "id": "action_submit_details",
                                "label": "{{ t(onboarding:forms.user_details.actions.next.label) }}",
                                "variant": "PRIMARY",
                                "eventType": "SUBMIT"
                            }
                        ]
                    }
                ]
            },
            "prompts": [
                {
                    "inputs": [
                        {
                            "ref": "input_prompt_username",
                            "identifier": "username",
                            "type": "TEXT_INPUT",
                            "required": true
                        },
                        {
                            "ref": "input_prompt_given_name",
                            "identifier": "given_name",
                            "type": "TEXT_INPUT",
                            "required": false
                        },
                        {
                            "ref": "input_prompt_family_name",
                            "identifier": "family_name",
                            "type": "TEXT_INPUT",
                            "required": false
                        }
                    ],
                    "action": {
                        "ref": "action_submit_details",
                        "nextNode": "check_user_details_uniqueness"
                    }
                }
            ]
        },
        {
            "id": "check_user_details_uniqueness",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "AttributeUniquenessValidator"
            },
            "onSuccess": "prompt_credential",
            "onIncomplete": "prompt_user_details"
        },
        {
            "id": "prompt_credential",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "text_header_pwd",
                        "label": "{{ t(onboarding:forms.credential.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "type": "BLOCK",
                        "id": "block_pwd",
                        "components": [
                            {
                                "id": "input_prompt_password",
                                "ref": "password",
                                "type": "PASSWORD_INPUT",
                                "label": "{{ t(onboarding:forms.credential.fields.password.label) }}",
                                "required": true,
                                "placeholder": "{{ t(onboarding:forms.credential.fields.password.placeholder) }}"
                            },
                            {
                                "type": "ACTION",
                                "id": "action_submit_pwd",
                                "label": "{{ t(onboarding:forms.credential.actions.submit.label) }}",
                                "variant": "PRIMARY",
                                "eventType": "SUBMIT"
                            }
                        ]
                    }
                ]
            },
            "prompts": [
                {
                    "inputs": [
                        {
                            "ref": "input_prompt_password",
                            "identifier": "password",
                            "type": "PASSWORD_INPUT",
                            "required": true
                        }
                    ],
                    "action": {
                        "ref": "action_submit_pwd",
                        "nextNode": "provisioning"
                    }
                }
            ]
        },
        {
            "id": "provisioning",
            "type": "TASK_EXECUTION",
            "executor": {
                "name": "ProvisioningExecutor",
                "inputs": [
                    {
                        "ref": "input_email",
                        "identifier": "workemail",
                        "type": "EMAIL_INPUT",
                        "required": true
                    },
                    {
                        "ref": "input_username",
                        "identifier": "username",
                        "type": "TEXT_INPUT",
                        "required": true
                    },
                    {
                        "ref": "input_password",
                        "identifier": "password",
                        "type": "PASSWORD_INPUT",
                        "required": true
                    },
                    {
                        "ref": "input_given_name",
                        "identifier": "given_name",
                        "type": "TEXT_INPUT",
                        "required": false
                    },
                    {
                        "ref": "input_family_name",
                        "identifier": "family_name",
                        "type": "TEXT_INPUT",
                        "required": false
                    }
                ]
            },
            "onSuccess": "registration_complete"
        },
        {
            "id": "registration_complete",
            "type": "PROMPT",
            "meta": {
                "components": [
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "registration_complete_icon",
                        "label": "",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "registration_complete_heading",
                        "label": "{{ t(invite:complete.title) }}",
                        "variant": "HEADING_1"
                    },
                    {
                        "align": "center",
                        "type": "TEXT",
                        "id": "registration_complete_message",
                        "label": "{{ t(invite:complete.description) }}"
                    }
                ]
            },
            "message": "Registration complete",
            "next": "end"
        },
        {
            "id": "end",
            "type": "END"
        }
    ]
}

Related Issues

  • N/A

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added runtime control to skip email delivery when needed
    • Enhanced email recipient resolution to support multiple sources including user attributes
    • Introduced support for configurable email input types
  • Tests

    • Expanded test coverage for email recipient resolution and delivery control
    • Added comprehensive tests for user attribute extraction functionality

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

The changes extend the email executor to support delivery skipping via runtime configuration and refactor recipient email resolution to support multiple data sources, including extraction via an entity provider. New constants and utility functions are introduced to support these capabilities.

Changes

Cohort / File(s) Summary
Constants
backend/internal/flow/common/constants.go
Added two new constants: RuntimeKeySkipDelivery for controlling delivery behavior and InputTypeEmail for email input type identification.
Email Executor Core
backend/internal/flow/executor/email_executor.go
Refactored recipient email resolution to support multiple sources (forwarded data, runtime data, user inputs) and entity provider lookup. Added delivery-skipping logic that returns early when RuntimeKeySkipDelivery is set. Updated constructor to accept entityProvider for fetching user attributes.
Email Executor Tests
backend/internal/flow/executor/email_executor_test.go
Expanded test coverage to include recipient resolution from various sources, delivery skipping scenarios, entity provider error handling, and nil provider configuration errors. Tightened mock expectations and replaced argument matchers with strict equality checks.
Executor Initialization
backend/internal/flow/executor/init.go
Updated email executor registration to pass entityProvider to the constructor.
User Attribute Utilities
backend/internal/flow/executor/utils.go, backend/internal/flow/executor/utils_test.go
Added GetUserAttribute function to extract string-typed attributes from user entities parsed from JSON. Includes comprehensive table-driven tests covering success and failure scenarios (nil user, invalid JSON, missing keys, non-string values).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ThaminduDilshan
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: flexible email schema support with default fallback in the Email Executor, which aligns with the PR's core objective of resolving flexible email attribute names from flow JSON configurations.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description includes the Purpose and Approach sections with clear explanation of flexible email attribute resolution, provides a detailed tested example JSON flow, and completes the checklist with most items marked.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.86%. Comparing base (60ca881) to head (256493a).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2461      +/-   ##
==========================================
+ Coverage   88.85%   88.86%   +0.01%     
==========================================
  Files         871      871              
  Lines       60244    60282      +38     
==========================================
+ Hits        53528    53571      +43     
+ Misses       4937     4930       -7     
- Partials     1779     1781       +2     
Flag Coverage Δ
backend-integration-postgres 53.33% <10.41%> (-0.05%) ⬇️
backend-integration-redis 52.97% <10.41%> (+0.02%) ⬆️
backend-integration-sqlite 53.33% <10.41%> (+0.02%) ⬆️
backend-unit 82.75% <95.83%> (+<0.01%) ⬆️
frontend-apps-console-unit 89.39% <ø> (ø)
frontend-apps-gate-unit 97.35% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RandithaK RandithaK force-pushed the feature/flex-schema-email-executor branch 2 times, most recently from 5aecc23 to 97d3af9 Compare April 27, 2026 08:28
@RandithaK
Copy link
Copy Markdown
Contributor Author

Please mark this as an improvement.

Note, that MagicLink implementation #1879 depends on this PR.

@RandithaK RandithaK marked this pull request as ready for review April 27, 2026 08:30
Copilot AI review requested due to automatic review settings April 27, 2026 08:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds dynamic recipient email resolution to EmailExecutor so flows can use a non-default email attribute identifier (e.g., workemail) and optionally fall back to resolving the attribute from the DB via EntityProvider.

Changes:

  • Add GetUserAttribute helper to extract a string attribute from a user entity’s JSON Attributes.
  • Enhance EmailExecutor to resolve recipient email using the configured EMAIL_INPUT identifier (user inputs/runtime/forwarded data), with DB fallback and a skipDelivery runtime flag.
  • Update unit tests and wiring to pass EntityProvider into EmailExecutor.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
backend/internal/flow/executor/utils.go Adds GetUserAttribute JSON attribute extraction helper.
backend/internal/flow/executor/utils_test.go Adds tests for GetUserAttribute.
backend/internal/flow/executor/email_executor.go Resolves recipient email using dynamic identifier + forwarded/DB fallback; adds skipDelivery.
backend/internal/flow/executor/email_executor_test.go Expands tests for new resolution paths and updates mocks/expectations.
backend/internal/flow/executor/init.go Wires entityProvider into newEmailExecutor.
backend/internal/flow/common/constants.go Adds RuntimeKeySkipDelivery, InputTypeEmail, and AttributeEmail.
Comments suppressed due to low confidence (1)

backend/internal/flow/executor/email_executor.go:62

  • The executor defines defaultEmailInput as EMAIL_INPUT, but the base executor is still registered with a default required input of type TEXT_INPUT (userAttributeEmail). If this executor ever falls back to default inputs (when ctx.NodeInputs is empty), the engine/UI will treat the required email as a text input. Consider using defaultEmailInput (and common.InputTypeEmail) for the base executor defaults to keep input typing consistent with the new dynamic-email logic.
	base := flowFactory.CreateExecutor(
		ExecutorNameEmailExecutor,
		common.ExecutorTypeUtility,
		[]common.Input{},
		[]common.Input{
			{Identifier: userAttributeEmail, Type: common.InputTypeText, Required: true},
		},

Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor_test.go Outdated
Comment thread backend/internal/flow/executor/email_executor.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
backend/internal/flow/executor/utils.go (1)

50-53: Optional: preserve the underlying JSON parse error.

errors.New("failed to parse user attributes") discards the original json.Unmarshal error, which can make debugging malformed Attributes payloads harder. Consider wrapping it.

♻️ Suggested change
-	if err := json.Unmarshal(user.Attributes, &attrs); err != nil {
-		return "", errors.New("failed to parse user attributes")
-	}
+	if err := json.Unmarshal(user.Attributes, &attrs); err != nil {
+		return "", fmt.Errorf("failed to parse user attributes: %w", err)
+	}

If you adopt this, the errors import can also be dropped since the only remaining errors.New at line 47 can be folded into a fmt.Errorf (or kept as-is).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/internal/flow/executor/utils.go` around lines 50 - 53, The
json.Unmarshal error for user.Attributes is being discarded; update the attrs
parse block (the variable attrs and the json.Unmarshal call) to wrap and return
the original error (e.g., using fmt.Errorf("failed to parse user attributes:
%w", err)) so the underlying JSON error is preserved for debugging, and after
that remove or consolidate the now-unnecessary errors import (you can replace
other errors.New uses with fmt.Errorf if desired).
backend/internal/flow/common/constants.go (1)

226-234: Merge the new AttributeEmail into the existing attribute const block.

AttributeMobileNumber and AttributeEmail are both "well-known user attribute" names (per the section comment at line 226). Splitting them across two const blocks fragments the section unnecessarily.

♻️ Suggested change
 // Attribute name constants for well-known user attributes used across flow executors.
 const (
 	// AttributeMobileNumber is the default attribute name for a user's mobile phone number.
 	AttributeMobileNumber = "mobileNumber"
-)
-const (
 	// AttributeEmail is the default attribute name for a user's email.
 	AttributeEmail = "email"
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/internal/flow/common/constants.go` around lines 226 - 234, Merge
AttributeEmail into the existing "well-known user attributes" const block so
both AttributeMobileNumber and AttributeEmail are declared together; update the
const block containing AttributeMobileNumber to also include AttributeEmail
(preserve comments for each constant) and remove the separate second const block
to avoid fragmenting the related attribute constants.
backend/internal/flow/executor/email_executor.go (1)

197-204: Optional: Simplify control flow by removing unnecessary else block.

The else block following a return statement is unnecessary and can be simplified. This improves readability by following the idiomatic Go pattern of handling error cases early.

♻️ Suggested change
-			if recipientEmail, err := GetUserAttribute(user, emailAttr); err == nil {
-				return recipientEmail
-			} else {
-				e.logger.Debug("Email attribute not found in user entity", log.String("attribute", emailAttr),
-					log.Error(err))
-			}
+			recipientEmail, err := GetUserAttribute(user, emailAttr)
+			if err == nil {
+				return recipientEmail
+			}
+			e.logger.Debug("Email attribute not found in user entity",
+				log.String("attribute", emailAttr), log.Error(err))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/internal/flow/executor/email_executor.go` around lines 197 - 204, The
block inside the loop uses an unnecessary else after a return; simplify by
handling the error case early and returning on success directly: in the loop
that calls GetUserAttribute (using recipientEmail, err, and emailAttr), remove
the else branch and instead, after checking err == nil return recipientEmail,
and otherwise call e.logger.Debug(...) for the error path; update the code
around GetUserAttribute and e.logger.Debug to follow this early-return idiom.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/internal/flow/executor/email_executor.go`:
- Around line 94-98: Add documentation under docs/content/guides that explains
the three user-visible changes introduced by EmailExecutor: (1) document the new
EMAIL_INPUT input type (common.InputTypeEmail) and show how to declare a node
input with type: "EMAIL_INPUT" and a custom identifier (e.g., workEmail) so flow
authors can drive recipient resolution (reference resolveEmailInput to show
where this applies); (2) document the skipDelivery runtime key
(common.RuntimeKeySkipDelivery) including its semantics and example usage to
bypass email sending (reference the early-return block that checks
RuntimeKeySkipDelivery); and (3) document the updated recipient resolution order
and DB fallback logic (UserInputs → RuntimeData → ForwardedData → entityProvider
lookup by userId) and call out the behavioral change where EmailExecutor reads
the email attribute from the user entity when not present in
inputs/runtime/forwarded data (reference the EmailExecutor recipient-resolution
code paths). Ensure the guide includes examples and any authoring notes for
existing flows that may be affected.

---

Nitpick comments:
In `@backend/internal/flow/common/constants.go`:
- Around line 226-234: Merge AttributeEmail into the existing "well-known user
attributes" const block so both AttributeMobileNumber and AttributeEmail are
declared together; update the const block containing AttributeMobileNumber to
also include AttributeEmail (preserve comments for each constant) and remove the
separate second const block to avoid fragmenting the related attribute
constants.

In `@backend/internal/flow/executor/email_executor.go`:
- Around line 197-204: The block inside the loop uses an unnecessary else after
a return; simplify by handling the error case early and returning on success
directly: in the loop that calls GetUserAttribute (using recipientEmail, err,
and emailAttr), remove the else branch and instead, after checking err == nil
return recipientEmail, and otherwise call e.logger.Debug(...) for the error
path; update the code around GetUserAttribute and e.logger.Debug to follow this
early-return idiom.

In `@backend/internal/flow/executor/utils.go`:
- Around line 50-53: The json.Unmarshal error for user.Attributes is being
discarded; update the attrs parse block (the variable attrs and the
json.Unmarshal call) to wrap and return the original error (e.g., using
fmt.Errorf("failed to parse user attributes: %w", err)) so the underlying JSON
error is preserved for debugging, and after that remove or consolidate the
now-unnecessary errors import (you can replace other errors.New uses with
fmt.Errorf if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d50033b3-6506-4e42-a07e-ba1742d2b030

📥 Commits

Reviewing files that changed from the base of the PR and between 4f22331 and 97d3af9.

📒 Files selected for processing (6)
  • backend/internal/flow/common/constants.go
  • backend/internal/flow/executor/email_executor.go
  • backend/internal/flow/executor/email_executor_test.go
  • backend/internal/flow/executor/init.go
  • backend/internal/flow/executor/utils.go
  • backend/internal/flow/executor/utils_test.go

Comment thread backend/internal/flow/executor/email_executor.go
Comment thread backend/internal/flow/common/constants.go Outdated
Comment thread backend/internal/flow/executor/email_executor.go Outdated
Comment thread backend/internal/flow/executor/email_executor.go Outdated
Comment thread backend/internal/flow/executor/email_executor.go Outdated
@RandithaK RandithaK changed the title Dynamic schema support and DB fallback to Email Executor Dynamic schema support with fallback in Email Executor Apr 27, 2026
@RandithaK RandithaK changed the title Dynamic schema support with fallback in Email Executor Flexible schema support with default fallback in Email Executor Apr 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/internal/flow/executor/constants.go (1)

129-132: Consolidate with the existing userAttributeEmail to avoid duplicate string literals.

AttributeEmail duplicates the value of userAttributeEmail defined at line 66. Two constants holding the same "email" string for the same purpose risk drift if one is ever changed. Either drop the unexported one in favor of the exported constant (and update internal references), or define one in terms of the other. Also, the new constant breaks the existing userAttribute* naming pattern used elsewhere in this file — consider UserAttributeEmail for consistency if external callers need it exported.

♻️ Proposed consolidation
@@
-	userAttributeUsername = "username"
-	userAttributePassword = "password"
-	userAttributeUserID   = "userID"
-	userAttributeEmail    = "email"
-	userAttributeGroups   = "groups"
-	userAttributeSub      = "sub"
+	userAttributeUsername = "username"
+	userAttributePassword = "password"
+	userAttributeUserID   = "userID"
+	userAttributeEmail    = AttributeEmail
+	userAttributeGroups   = "groups"
+	userAttributeSub      = "sub"
@@
-const (
-	// AttributeEmail is the default attribute name for a user's email.
-	AttributeEmail = "email"
-)
+// User attribute name constants exported for cross-package use.
+const (
+	// AttributeEmail is the default attribute name for a user's email.
+	AttributeEmail = "email"
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/internal/flow/executor/constants.go` around lines 129 - 132, The file
introduces a duplicate "email" constant: AttributeEmail and the existing
userAttributeEmail; remove the duplication by consolidating them—either delete
AttributeEmail and replace its references with userAttributeEmail (or
vice‑versa), or define one in terms of the other (e.g., AttributeEmail =
userAttributeEmail) to keep a single source of truth; if the exported name is
required by other packages, prefer renaming to a consistent exported identifier
(e.g., UserAttributeEmail) and update all usages accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/internal/flow/executor/constants.go`:
- Around line 129-132: The file introduces a duplicate "email" constant:
AttributeEmail and the existing userAttributeEmail; remove the duplication by
consolidating them—either delete AttributeEmail and replace its references with
userAttributeEmail (or vice‑versa), or define one in terms of the other (e.g.,
AttributeEmail = userAttributeEmail) to keep a single source of truth; if the
exported name is required by other packages, prefer renaming to a consistent
exported identifier (e.g., UserAttributeEmail) and update all usages
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 945c8e75-5fbc-407f-89ac-988ad0957a38

📥 Commits

Reviewing files that changed from the base of the PR and between 97d3af9 and 923773e.

📒 Files selected for processing (3)
  • backend/internal/flow/common/constants.go
  • backend/internal/flow/executor/constants.go
  • backend/internal/flow/executor/email_executor.go
✅ Files skipped from review due to trivial changes (1)
  • backend/internal/flow/common/constants.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/internal/flow/executor/email_executor.go

@RandithaK RandithaK force-pushed the feature/flex-schema-email-executor branch 4 times, most recently from 96a13f1 to e2161c5 Compare April 27, 2026 10:27
Comment thread backend/internal/flow/executor/constants.go Outdated
@RandithaK RandithaK force-pushed the feature/flex-schema-email-executor branch 5 times, most recently from e1c0548 to 1d55815 Compare April 28, 2026 03:36
Signed-off-by: RandithaK <me@randitha.net>
@RandithaK RandithaK force-pushed the feature/flex-schema-email-executor branch from 1d55815 to 256493a Compare April 28, 2026 03:59
@ThaminduDilshan ThaminduDilshan added this pull request to the merge queue Apr 28, 2026
Merged via the queue into asgardeo:main with commit 2e1d12c Apr 28, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants