@@ -22,6 +22,7 @@ import (
2222 "errors"
2323 "fmt"
2424
25+ "github.com/asgardeo/thunder/internal/entityprovider"
2526 "github.com/asgardeo/thunder/internal/flow/common"
2627 "github.com/asgardeo/thunder/internal/flow/core"
2728 "github.com/asgardeo/thunder/internal/system/email"
@@ -36,11 +37,21 @@ type emailExecutor struct {
3637 logger * log.Logger
3738 emailClient email.EmailClientInterface
3839 templateService template.TemplateServiceInterface
40+ entityProvider entityprovider.EntityProviderInterface
41+ }
42+
43+ // defaultEmailInput is the default input definition for email collection.
44+ var defaultEmailInput = common.Input {
45+ Ref : "email_input" ,
46+ Identifier : common .AttributeEmail ,
47+ Type : common .InputTypeEmail ,
48+ Required : true ,
3949}
4050
4151// newEmailExecutor creates a new instance of the email executor.
4252func newEmailExecutor (flowFactory core.FlowFactoryInterface , emailClient email.EmailClientInterface ,
43- templateService template.TemplateServiceInterface ) * emailExecutor {
53+ templateService template.TemplateServiceInterface ,
54+ entityProvider entityprovider.EntityProviderInterface ) * emailExecutor {
4455 logger := log .GetLogger ().With (log .String (log .LoggerKeyComponentName , "EmailExecutor" ))
4556 base := flowFactory .CreateExecutor (
4657 ExecutorNameEmailExecutor ,
@@ -55,6 +66,7 @@ func newEmailExecutor(flowFactory core.FlowFactoryInterface, emailClient email.E
5566 logger : logger ,
5667 emailClient : emailClient ,
5768 templateService : templateService ,
69+ entityProvider : entityProvider ,
5870 }
5971}
6072
@@ -79,6 +91,12 @@ func (e *emailExecutor) executeSend(ctx *core.NodeContext) (*common.ExecutorResp
7991 RuntimeData : make (map [string ]string ),
8092 }
8193
94+ if ctx .RuntimeData [common .RuntimeKeySkipDelivery ] == dataValueTrue {
95+ logger .Debug ("Delivery marked as skipped, completing without sending email" )
96+ execResp .Status = common .ExecComplete
97+ return execResp , nil
98+ }
99+
82100 if e .emailClient == nil {
83101 execResp .AdditionalData [common .DataEmailSent ] = dataValueFalse
84102 execResp .Status = common .ExecFailure
@@ -92,7 +110,7 @@ func (e *emailExecutor) executeSend(ctx *core.NodeContext) (*common.ExecutorResp
92110 }
93111
94112 // Resolve recipient email from user inputs or runtime data.
95- recipient := resolveRecipientEmail (ctx )
113+ recipient := e . resolveRecipientEmail (ctx )
96114 if recipient == "" {
97115 logger .Debug ("Email recipient not found in user inputs or runtime data" )
98116 execResp .Status = common .ExecFailure
@@ -125,7 +143,6 @@ func (e *emailExecutor) executeSend(ctx *core.NodeContext) (*common.ExecutorResp
125143 "inviteLink" : inviteLink ,
126144 "appName" : ctx .Application .Name ,
127145 }
128-
129146 rendered , svcErr := e .templateService .Render (ctx .Context , scenario , template .TemplateTypeEmail , templateData )
130147 if svcErr != nil {
131148 return nil , fmt .Errorf ("failed to render email template: %s" , svcErr .Code )
@@ -156,14 +173,36 @@ func (e *emailExecutor) executeSend(ctx *core.NodeContext) (*common.ExecutorResp
156173 return execResp , nil
157174}
158175
159- // resolveRecipientEmail retrieves the recipient email from user inputs or runtime data.
160- func resolveRecipientEmail (ctx * core.NodeContext ) string {
161- if recipientEmail , ok := ctx .UserInputs [userAttributeEmail ]; ok && recipientEmail != "" {
176+ // resolveRecipientEmail retrieves the recipient email from user inputs, runtime data, or forwarded data.
177+ func (e * emailExecutor ) resolveRecipientEmail (ctx * core.NodeContext ) string {
178+ emailAttr := e .resolveEmailInput (ctx ).Identifier
179+
180+ if recipientEmail , ok := ctx .UserInputs [emailAttr ]; ok && recipientEmail != "" {
162181 return recipientEmail
163182 }
164- if recipientEmail , ok := ctx .RuntimeData [userAttributeEmail ]; ok && recipientEmail != "" {
183+ if recipientEmail , ok := ctx .RuntimeData [emailAttr ]; ok && recipientEmail != "" {
165184 return recipientEmail
166185 }
186+ if recipientEmail , ok := ctx .ForwardedData [emailAttr ]; ok {
187+ if emailStr , isString := recipientEmail .(string ); isString && emailStr != "" {
188+ return emailStr
189+ }
190+ }
191+
192+ if userID , ok := ctx .RuntimeData [userAttributeUserID ]; ok && userID != "" && e .entityProvider != nil {
193+ user , providerErr := e .entityProvider .GetEntity (userID )
194+ if providerErr != nil {
195+ e .logger .Error ("Failed to fetch user from entity provider for email resolution" , log .Error (providerErr ))
196+ } else if user != nil {
197+ if recipientEmail , err := GetUserAttribute (user , emailAttr ); err == nil {
198+ return recipientEmail
199+ } else {
200+ e .logger .Debug ("Email attribute not found in user entity" , log .String ("attribute" , emailAttr ),
201+ log .Error (err ))
202+ }
203+ }
204+ }
205+
167206 return ""
168207}
169208
@@ -180,3 +219,14 @@ func isEmailError(err error) bool {
180219 errors .Is (err , email .ErrorSMTPAuth ) ||
181220 errors .Is (err , email .ErrorEmailSendFailed )
182221}
222+
223+ // resolveEmailInput returns the EMAIL_INPUT definition from the node context inputs,
224+ // falling back to the default if none is found.
225+ func (e * emailExecutor ) resolveEmailInput (ctx * core.NodeContext ) common.Input {
226+ for _ , input := range ctx .NodeInputs {
227+ if input .Type == common .InputTypeEmail {
228+ return input
229+ }
230+ }
231+ return defaultEmailInput
232+ }
0 commit comments