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 .= " <a href='javascript:open_email_template_form(".$line.")' >".translate('LBL_CREATE_EMAIL_TEMPLATE', 'AOW_Actions')."</a>";
$html .= " <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;
}
``