Different Sender E-Mail Adresses for Workflow E-Mails

I will share with you a code based solution that allowed me to achieve what I want without using paid plugins.

You will have to allow the params from the workflow to also allow to add a “from address” and then change the send email action to be able to work with that parameter. However, for that to work you need to edit the function edit_display to allow to introduce a from address in the form. I will share with you what I did

First you need to change the /include/language/es_es.lang.php and add the following line

$app_list_strings[‘aow_email_to_list_from’][‘from’] = ‘From’;

Then you need to change the javascript file that interacts with the responsive part of the edit_display function add_emailLine in the file AOW_Actions/actions/actionSendEmail.js so it allows the field from to be placed in the aow_actions parameters

function add_emailLine(ln){

    var aow_email_type_list = document.getElementById("aow_email_type_list").value;
    var aow_email_to_list = document.getElementById("aow_email_to_list").value;

    if (emailln[ln] == null) {
        emailln[ln] = 1; // Start from 1 to avoid conflicting with the single "from" entry
    }

    tablebody = document.createElement("tbody");
    tablebody.id = 'emailLine'+ln+'_body' + emailln[ln];
    document.getElementById('emailLine'+ln+'_table').appendChild(tablebody);

    var x = tablebody.insertRow(-1);
    x.id = 'emailLine'+ln+'_line' + emailln[ln];

    var a = x.insertCell(0);
    a.innerHTML = "<button type='button' id='emailLine"+ln+"_delete" + emailln[ln]+"' class='button' value='Remove Line' tabindex='116' onclick='clear_emailLine(" + ln + ",this);'><span class='suitepicon suitepicon-action-minus'></span></button> ";

    // Remove the "from" option from other dropdowns
    aow_email_to_list = aow_email_to_list.replace(/<option value='from'>.*?<\/option>/, '');

    a.innerHTML += "<select tabindex='116' name='aow_actions_param["+ln+"][email_to_type]["+emailln[ln]+"]' id='aow_actions_param"+ln+"_email_to_type"+emailln[ln]+"'>" + aow_email_to_list + "</select> ";
    a.innerHTML += "<select tabindex='116' name='aow_actions_param["+ln+"][email_target_type]["+emailln[ln]+"]' id='aow_actions_param"+ln+"_email_target_type"+emailln[ln]+"' onchange='show_emailField(" + ln + "," + emailln[ln] + ");'>" + aow_email_type_list + "</select> ";
    a.innerHTML += "<span id='emailLine"+ln+"_field"+emailln[ln]+"'><input id='aow_actions_param["+ln+"][email]["+emailln[ln]+"]' type='text' tabindex='116' size='25' name='aow_actions_param["+ln+"][email]["+emailln[ln]+"]'></span>";

    emailln[ln]++;

    return emailln[ln] - 1;
}

Next, you will need to change the following functions in the file AOW_Actions/actions/actionSendEmail.php

edit_display

    public function edit_display($line, SugarBean $bean = null, $params = array())
    {
        global $app_list_strings;
        $email_templates_arr = get_bean_select_array(true, 'EmailTemplate', 'name', '', 'name');

        if (!in_array($bean->module_dir, getEmailableModules())) {
            unset($app_list_strings['aow_email_type_list']['Record Email']);
        }
        $targetOptions = getRelatedEmailableFields($bean->module_dir);
        if (empty($targetOptions)) {
            unset($app_list_strings['aow_email_type_list']['Related Field']);
        }

        // Prepare options without 'from' in the list
        $aow_email_to_list = $app_list_strings['aow_email_to_list'];
        unset($aow_email_to_list['from']);
        $to_list_options = get_select_options_with_id($aow_email_to_list, '');

        // Prepare hidden fields for select options
        $html = '<input type="hidden" name="aow_email_type_list" id="aow_email_type_list" value="' . get_select_options_with_id($app_list_strings['aow_email_type_list'], '') . '">
                 <input type="hidden" name="aow_email_to_list" id="aow_email_to_list" value="' . $to_list_options . '">';

        $checked = isset($params['individual_email']) && $params['individual_email'] ? 'CHECKED' : '';

        // Start the HTML structure for the Form elements
        $html .= "<table border='0' cellpadding='0' cellspacing='0' width='100%' data-workflow-action='send-email'>";
        $html .= "<tr>";
        $html .= '<td valign="top" scope="row"><label>' . translate("LBL_INDIVIDUAL_EMAILS", "AOW_Actions") . ':</label></td>';
        $html .= "<td valign='top'>";
        $html .= "<input type='hidden' name='aow_actions_param[".$line."][individual_email]' value='0' >";
        $html .= "<input type='checkbox' id='aow_actions_param[".$line."][individual_email]' name='aow_actions_param[".$line."][individual_email]' value='1' $checked></td>";
        $html .= '</td>';

        $params['email_template'] = isset($params['email_template']) ? $params['email_template'] : '';
        $hidden = $params['email_template'] != '' ? "" : "style='visibility: hidden;'";

        $html .= '<td id="name_label" scope="row" valign="top"><label>' . translate("LBL_EMAIL_TEMPLATE", "AOW_Actions") . ':<span class="required">*</span></label></td>';
        $html .= "<td valign='top'>";
        $html .= "<select name='aow_actions_param[".$line."][email_template]' id='aow_actions_param_email_template".$line."' onchange='show_edit_template_link(this," . $line . ");'>" . get_select_options_with_id($email_templates_arr, $params['email_template'])."</select>";
        $html .= "&nbsp;<a href='javascript:open_email_template_form(".$line.")' >".translate('LBL_CREATE_EMAIL_TEMPLATE', 'AOW_Actions')."</a>";
        $html .= "&nbsp;<span name='edit_template' id='aow_actions_edit_template_link".$line."' $hidden><a href='javascript:edit_email_template_form(".$line.")' >".translate('LBL_EDIT_EMAIL_TEMPLATE', 'AOW_Actions')."</a></span>";
        $html .= "</td>";
        $html .= "</tr>";
        // Single line email input for "From" address using separate translation and numeric index
        $html .= "<tr>";
        $html .= "<td id='from_label' scope='row' valign='top'><label>Email:</label></td>";
        $html .= "<td colspan='3'>";
        $html .= "<table id='singleEmailLine" . $line . "_table' width='100%' class='email-line' style='margin-bottom: 10px;'>";
        $html .= "<tr><td>";
        $html .= "<select tabindex='116' name='aow_actions_param[" . $line . "][email_to_type][0]' id='aow_actions_param" . $line . "_email_to_type0'>";
        $html .= "<option value='from'>From</option>";
        $html .= "</select>";
        $html .= "<select tabindex='116' name='aow_actions_param[" . $line . "][email_target_type][0]' id='aow_actions_param" . $line . "_email_target_type0' onchange='show_emailField(" . $line . ", 0);'>";
        $html .= get_select_options_with_id($app_list_strings['aow_email_type_list'], 'Email Address');
        $html .= "</select>";
        $html .= "<span id='emailLine" . $line . "_field0'><input id='aow_actions_param[" . $line . "][email][0]' type='text' tabindex='116' size='25' name='aow_actions_param[" . $line . "][email][0]' value='" . (isset($params['email'][0]) ? $params['email'][0] : '') . "'></span>";
        $html .= "</td></tr>";
        $html .= "</table>";
        $html .= "</td>";
        $html .= "</tr>";

        // "To", "CC", "BCC" fields table
        $html .= "<tr>";
        $html .= '<td id="to_label" scope="row" valign="top" style="display:none;"><label>Para:</label></td>';
        $html .= '<td colspan="3">';
        $html .= '<button type="button" onclick="add_emailLine(' . $line . ')" id="plus-icon"><span class="suitepicon suitepicon-action-plus"></span></button>';
        $html .= '<table id="emailLine' . $line . '_table" width="100%" class="email-line">';

        // If there are no existing lines, add a default one
        if (!isset($params['email_target_type']) || empty($params['email_target_type'])) {
            $html .= '<tbody id="emailLine'.$line.'_body0">';
            $html .= '<tr id="emailLine'.$line.'_line0">';
            $html .= '<td><button type="button" id="emailLine'.$line.'_delete0" class="button" value="Remove Line" tabindex="116" onclick="clear_emailLine(' . $line . ', this);"><span class="suitepicon suitepicon-action-minus"></span></button> ';
            $html .= '<select tabindex="116" name="aow_actions_param['.$line.'][email_to_type][1]" id="aow_actions_param'.$line.'_email_to_type1">' . $to_list_options . '</select> ';
            $html .= '<select tabindex="116" name="aow_actions_param['.$line.'][email_target_type][1]" id="aow_actions_param'.$line.'_email_target_type1" onchange="show_emailField(' . $line . ',1);">' . get_select_options_with_id($app_list_strings['aow_email_type_list'], '') . '</select> ';
            $html .= '<span id="emailLine'.$line.'_field1"><input id="aow_actions_param['.$line.'][email][1]" type="text" tabindex="116" size="25" name="aow_actions_param['.$line.'][email][1]" value=""></span></td>';
            $html .= '</tr>';
            $html .= '</tbody>';
        }

        $html .= '</table>';
        $html .= '</td>';
        $html .= "</tr>";
        $html .= "</table>";

        // JavaScript initialization
        $html .= "<script id ='aow_script".$line."'>";
        if (isset($params['email_target_type'])) {
            foreach ($params['email_target_type'] as $key => $field) {
                // Ignore lines where email_to_type is "from"
                if ($params['email_to_type'][$key] === 'from') {
                    continue;
                }
                if (is_array($params['email'][$key])) {
                    $params['email'][$key] = json_encode($params['email'][$key]);
                }
                $html .= "load_emailline('".$line."','".$params['email_to_type'][$key]."','".$params['email_target_type'][$key]."','".$params['email'][$key]."');";
            }
        }
        $html .= "</script>";

        return $html;
    }

Then the getEmailsFromParams function

    protected function getEmailsFromParams(SugarBean $bean, $params)
    {
        $emails = array();
        $emailFrom = null;
        //backward compatible
        if (isset($params['email_target_type']) && !is_array($params['email_target_type'])) {
            $email = '';
            switch ($params['email_target_type']) {
                case 'Email Address':
                    $params['email'] = array($params['email']);
                    break;
                case 'Specify User':
                    $params['email'] = array($params['email_user_id']);
                    break;
                case 'Related Field':
                    $params['email'] = array($params['email_target']);
                    break;
            }
            $params['email_target_type'] = array($params['email_target_type']);
            $params['email_to_type'] = array('to');
        }
        //end backward compatible
        if (isset($params['email_target_type'])) {
            foreach ($params['email_target_type'] as $key => $field) {
                switch ($field) {
                    case 'Email Address':
                        if (trim($params['email'][$key]) != '') {
                            if ($params['email_to_type'][$key] === 'from') {
                                $emailFrom = $params['email'][$key];  // Set the 'from' email address
                            } else {
                                $emails[$params['email_to_type'][$key]][] = $params['email'][$key];
                            }
                        }
                        break;
                    case 'Specify User':
                        $user = BeanFactory::newBean('Users');
                        $user->retrieve($params['email'][$key]);
                        $user_email = $user->emailAddress->getPrimaryAddress($user);
                        if (trim($user_email) != '') {
                            if ($params['email_to_type'][$key] === 'from') {
                                $emailFrom = $user_email;  // Set the 'from' email address
                            } else {
                                $emails[$params['email_to_type'][$key]][] = $user_email;
                                $emails['template_override'][$user_email] = array('Users' => $user->id);
                            }
                        }
                        break;
                    case 'Users':
                        $users = array();
                        switch ($params['email'][$key][0]) {
                            case 'security_group':
                                if (file_exists('modules/SecurityGroups/SecurityGroup.php')) {
                                    require_once('modules/SecurityGroups/SecurityGroup.php');
                                    $security_group = BeanFactory::newBean('SecurityGroups');
                                    $security_group->retrieve($params['email'][$key][1]);
                                    $users = $security_group->get_linked_beans('users', 'User');
                                    $r_users = array();
                                    if ($params['email'][$key][2] != '') {
                                        require_once('modules/ACLRoles/ACLRole.php');
                                        $role = BeanFactory::newBean('ACLRoles');
                                        $role->retrieve($params['email'][$key][2]);
                                        $role_users = $role->get_linked_beans('users', 'User');
                                        foreach ($role_users as $role_user) {
                                            $r_users[$role_user->id] = $role_user->name;
                                        }
                                    }
                                    foreach ($users as $user_id => $user) {
                                        if ($params['email'][$key][2] != '' && !isset($r_users[$user->id])) {
                                            unset($users[$user_id]);
                                        }
                                    }
                                    break;
                                }
                            //No Security Group module found - fall through.
                            // no break
                            case 'role':
                                require_once('modules/ACLRoles/ACLRole.php');
                                $role = BeanFactory::newBean('ACLRoles');
                                $role->retrieve($params['email'][$key][2]);
                                $users = $role->get_linked_beans('users', 'User');
                                break;
                            case 'all':
                            default:
                                $db = DBManagerFactory::getInstance();
                                $sql = "SELECT id from users WHERE status='Active' AND portal_only=0 ";
                                $result = $db->query($sql);
                                while ($row = $db->fetchByAssoc($result)) {
                                    $user = BeanFactory::newBean('Users');
                                    $user->retrieve($row['id']);
                                    $users[$user->id] = $user;
                                }
                                break;
                        }
                        foreach ($users as $user) {
                            $user_email = $user->emailAddress->getPrimaryAddress($user);
                            if (trim($user_email) != '') {
                                $emails[$params['email_to_type'][$key]][] = $user_email;
                                $emails['template_override'][$user_email] = array('Users' => $user->id);
                            }
                        }
                        break;
                    case 'Related Field':
                        $emailTarget = $params['email'][$key];
                        $relatedFields = array_merge($bean->get_related_fields(), $bean->get_linked_fields());
                        $field = $relatedFields[$emailTarget];
                        if ($field['type'] == 'relate') {
                            $linkedBeans = array();
                            $idName = $field['id_name'];
                            $id = $bean->$idName;
                            if(gettype($id)=='string') {
                                $linkedBeans[] = BeanFactory::getBean($field['module'], $id);
                            } else {
                                $id = reset($id->get());
                                $linkedBeans[] = BeanFactory::getBean($field['module'], $id);
                            }
                        } else {
                            if ($field['type'] == 'link') {
                                $relField = $field['name'];
                                if (isset($field['module']) && $field['module'] != '') {
                                    $rel_module = $field['module'];
                                } else {
                                    if ($bean->load_relationship($relField)) {
                                        $rel_module = $bean->$relField->getRelatedModuleName();
                                    }
                                }
                                $linkedBeans = $bean->get_linked_beans($relField, $rel_module);
                            } else {
                                $linkedBeans = $bean->get_linked_beans($field['link'], $field['module']);
                            }
                        }
                        if ($linkedBeans) {
                            foreach ($linkedBeans as $linkedBean) {
                                if (!empty($linkedBean)) {
                                    $rel_email = $linkedBean->emailAddress->getPrimaryAddress($linkedBean);
                                    if (trim($rel_email) != '') {
                                        $emails[$params['email_to_type'][$key]][] = $rel_email;
                                        $emails['template_override'][$rel_email] = array($linkedBean->module_dir => $linkedBean->id);
                                    }
                                }
                            }
                        }
                        break;
                    case 'Record Email':
                        $recordEmail = $bean->emailAddress->getPrimaryAddress($bean);
                        if ($recordEmail == '' && isset($bean->email1)) {
                            $recordEmail = $bean->email1;
                        }
                        if (trim($recordEmail) != '') {
                            $emails[$params['email_to_type'][$key]][] = $recordEmail;
                        }
                        break;
                }
            }
        }
        $emails['from'] = $emailFrom;  // Add the 'from' email address to the array
        return $emails;
    }

Finally you will add two custom functions and edit the sendEmail so you can send emails through outboundemails defined in the system

    protected function getUserMailerSettingsByFromAddress($emailFrom)
    {
        $db = DBManagerFactory::getInstance();

        // Query the outbound_email table to fetch the settings where mail_smtpuser matches the emailFrom
        $query = "
            SELECT id
            FROM outbound_email
            WHERE mail_smtpuser = '" . $db->quote($emailFrom) . "' 
            AND deleted = 0 
            LIMIT 1";

        $result = $db->query($query);
        $outbound = $db->fetchByAssoc($result);

        // If outbound email settings are found, retrieve the OutboundEmailAccounts object
        if ($outbound) {
            $outboundEmailAccount = BeanFactory::getBean('OutboundEmailAccounts', $outbound['id']);
            return $outboundEmailAccount;
        }

        // If no specific settings found, fallback to system mailer settings
        $GLOBALS['log']->fatal("No specific outbound email settings found for: " . $emailFrom);
        $oe = new OutboundEmail();
        return $oe->getSystemMailerSettings();
    }


    protected function setupMailerFromOutbound(OutboundEmailAccounts $oe, SugarPHPMailer $mail): void
    {
        // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
        $mail->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
        if (isSmtp($oe->mail_sendtype ?? '')) {
            //Set mail send type information
            $mail->Mailer = "smtp";
            $mail->Host = $oe->mail_smtpserver;
            $mail->Port = $oe->mail_smtpport;
            if ($oe->mail_smtpssl == 1) {
                $mail->SMTPSecure = 'ssl';
            } // if
            if ($oe->mail_smtpssl == 2) {
                $mail->SMTPSecure = 'tls';
            } // if

            if ($oe->mail_smtpauth_req) {
                $mail->SMTPAuth = true;
                $mail->Username = $oe->mail_smtpuser;
                $mail->Password = $oe->mail_smtppass;
            }
        } else {
            $mail->Mailer = "sendmail";
        }
    }

    public function sendEmail($emailTo, $emailSubject, $emailBody, $altemailBody, SugarBean $relatedBean = null, $emailCc = array(), $emailBcc = array(), $attachments = array(), $emailFrom = null)
    {
        require_once('modules/Emails/Email.php');
        require_once('include/SugarPHPMailer.php');
        require_once('include/OutboundEmail/OutboundEmail.php');  // To fetch credentials

        $emailObj = BeanFactory::newBean('Emails');
        $defaults = $emailObj->getSystemDefaultEmail();
        $mail = new SugarPHPMailer();
        $mail->setMailerForSystem();

        // $GLOBALS['log']->fatal("Default system email: " . print_r($defaults['email'], true));
        // $GLOBALS['log']->fatal("Provided emailFrom: " . print_r($emailFrom, true));

        // Use the provided 'from' address if available; otherwise, use the system default
        $mail->From = $emailFrom ? $emailFrom : $defaults['email'];
        isValidEmailAddress($mail->From);  // Ensure the 'from' email is valid
        $mail->FromName = $emailFrom ? $emailFrom : $defaults['name'];

        // Check if the email is from the seepmode.com domain (Default System Domain)
        if (strpos($emailFrom, '@seepmode.com') !== false) {
            // Use the system mail settings for seepmode.com
            $GLOBALS['log']->fatal("Using system email settings for: " . $defaults['email']);
        } else {
            // Fetch the outbound email settings based on the emailFrom address
            $outbound = $this->getUserMailerSettingsByFromAddress($emailFrom);

            // Handle the case where no settings are found
            if (!$outbound || !$outbound instanceof OutboundEmailAccounts) {
                $GLOBALS['log']->fatal("No valid SMTP settings found for: " . $emailFrom);
                return false;  // Fail if no SMTP settings are found
            }

            // Set up the mailer using the extracted settings
            $this->setupMailerFromOutbound($outbound, $mail);
            // $GLOBALS['log']->fatal("Using SMTP credentials for: " . $emailFrom);
        }

        // $GLOBALS['log']->fatal("Final From email used: " . print_r($mail->From, true));

        $mail->ClearAllRecipients();
        $mail->ClearReplyTos();
        $mail->Subject = from_html($emailSubject);
        $mail->Body = $emailBody;
        $mail->AltBody = $altemailBody;
        $mail->handleAttachments($attachments);
        $mail->prepForOutbound();

        if (empty($emailTo) || !is_array($emailTo)) {
            $GLOBALS['log']->fatal("Email sending failed: No valid recipients found.");
            return false;
        }
        foreach ($emailTo as $to) {
            $mail->AddAddress($to);
        }
        if (!empty($emailCc)) {
            foreach ($emailCc as $email) {
                $mail->AddCC($email);
            }
        }
        if (!empty($emailBcc)) {
            foreach ($emailBcc as $email) {
                $mail->AddBCC($email);
            }
        }
        if (!is_array($emailCc)) {
            $emailCc = [];
        }

        if (!is_array($emailBcc)) {
            $emailBcc = [];
        }

        //now create email
        if ($mail->Send()) {
            // $GLOBALS['log']->fatal("Email sent successfully to: " . implode(',', $emailTo));
            $emailObj->to_addrs = implode(',', $emailTo);
            $emailObj->cc_addrs = implode(',', $emailCc);
            $emailObj->bcc_addrs = implode(',', $emailBcc);
            $emailObj->type = 'out';
            $emailObj->deleted = '0';
            $emailObj->name = $mail->Subject;
            $emailObj->description = $mail->AltBody;
            $emailObj->description_html = $mail->Body;
            $emailObj->from_addr = $mail->From;
            isValidEmailAddress($emailObj->from_addr);
            if ($relatedBean instanceof SugarBean && !empty($relatedBean->id)) {
                $emailObj->parent_type = $relatedBean->module_dir;
                $emailObj->parent_id = $relatedBean->id;
            }
            $emailObj->date_sent_received = TimeDate::getInstance()->nowDb();
            $emailObj->modified_user_id = '1';
            $emailObj->created_by = '1';
            $emailObj->status = 'sent';
            $emailObj->save();

            // Fix for issue 1561 - Email Attachments Sent By Workflow Do Not Show In Related Activity.
            foreach ($attachments as $attachment) {
                $note = BeanFactory::newBean('Notes');
                $note->id = create_guid();
                $note->date_entered = $attachment->date_entered;
                $note->date_modified = $attachment->date_modified;
                $note->modified_user_id = $attachment->modified_user_id;
                $note->assigned_user_id = $attachment->assigned_user_id;
                $note->new_with_id = true;
                $note->parent_id = $emailObj->id;
                $note->parent_type = $attachment->parent_type;
                $note->name = $attachment->name;
                ;
                $note->filename = $attachment->filename;
                $note->file_mime_type = $attachment->file_mime_type;
                $fileLocation = "upload://{$attachment->id}";
                $dest = "upload://{$note->id}";
                if (!copy($fileLocation, $dest)) {
                    $GLOBALS['log']->debug("EMAIL 2.0: could not copy attachment file to $fileLocation => $dest");
                }
                $note->save();
            }
            return true;
        } else {
            // Log the error from PHPMailer
            $GLOBALS['log']->fatal("Email sending failed. Error: " . $mail->ErrorInfo);
        }
        return false;
    }
``
2 Likes