How to add cc emails to new email

Hello,

  I need to find a way to include the people that are CC'ed in an email to appear when they email form the record view.

They then click the plus btn to generate a new email and this div appears to do that.

How would I start the process of automatically adding the CC'ed peoples to this view?

So to clarify I want the people that were CC'ed before in the case's history to automatically appear when clicking the plus btn and the user won't need to do it manually then.

Best Regards

James Palmisano

Parents
  • I had a similar requirement, the problem is that when you clcick the + on the Email subpanel on Cases you don't know WHICH email you are replying to.

    I solved the issue by creating a "reply all" option in rowactions ( on each line of the Email Subpanel ), by selecting that reply you know which email you are replying to.

    It was not easy.

    In

    custom/modules/Emails/clients/base/views/<your cases subpanel view>/<your cases subpanel view>.php

    I added three custom email actions: reply, reply all and forward (opendetails ia a custom view of the email thread)

    They behave as you would expect in an email client.

      'rowactions' => array (
        'actions' => array (
          array(
            'type' => 'rowaction',
            'css_class' => 'btn',
            'event' => 'list:customemailreply:fire',
            'tooltip' => 'Reply to email',
            'icon' => 'fa-reply',
            'acl_action' => 'create',
          ),
          array(
            'type' => 'rowaction',
            'event' => 'list:customemailreplyall:fire',
            'label'=>'LBL_REPLY_ALL',
            'tooltip' => 'Reply All',
            'icon' => 'fa-mail-reply-all',
            'acl_action' => 'create',
          ),
          array(
            'type' => 'rowaction',
            'event' => 'list:customemailforward:fire',
            'label'=>'LBL_FORWARD',
            'tooltip' => 'forward',
            'icon' => 'fa-mail-forward',
            'acl_action' => 'create',
          ),
    
          array(
            'type' => 'rowaction',
            'event' => 'list:opendetails:fire',
            'label' => 'LBL_OPEN_EMAIL_DETAILS',
            'tooltip' => 'Read email',
            'acl_action' => 'create',
          ),
        ),
    

     

    In the controller for the same view:

    custom/modules/Emails/clients/base/views/<your cases subpanel view>/<your cases subpanel view>.js

    I defined the actions to be taken when each of the events is fired.

    ({
      extendsFrom:'SubpanelListView',
      initialize: function(options){
        this._super('initialize', [options]);
        this.context.on('list:customemailreply:fire',this.customReplyClicked);
        this.context.on('list:customemailreplyall:fire',this.customReplyAllClicked);
        this.context.on('list:customemailforward:fire',this.customForwardClicked);
        this.context.on('list:opendetails:fire',this.opendetailsClicked);
      },
      opendetailsClicked: function(emailModel){
        //show progress message
        app.alert.show('loading-details', {
          level: 'info',
          messages: 'Loading details...',
          autoClose: false
        });
        var url = app.api.buildURL('getCustomEmailDetails/'+emailModel.get('id'));
        app.api.call('read', url, null, {
          success: _.bind(function(data) {
            console.log(data);
            app.alert.dismiss('loading-details');
            app.drawer.open({
              layout:'custom-email-detail',
              context:{
                emailDetails: data,
              }
            });
          })
        });
      },
      customForwardClicked: function(emailModel){
        var caseModel = this.get('parentModel'),
            caseId = caseModel.get('id'),
            emailType = emailModel.get('status');
        if(emailType == 'draft'){
          emailModel.set('isNotEmpty', true);
          //editing a draft
          //open the drawer with the email  as was when last saved
          app.drawer.open({
            layout:'compose',
            context:{
              create: false,
              model: emailModel,
              module: 'Emails',
            }
          },this);
        }else{
          var emailId = emailModel.get('id'),
              messageType = 'forward',//internal, reply, replyAll, forward or new
              url = app.api.buildURL('Emails/getCustomCompose/'+messageType+'/'+caseId+'/'+emailId);
          //new forwardpre-populate some information
          App.api.call('GET', url, '',{
            success: _.bind(function(o){
              app.drawer.open({
                layout:'compose',
                context:{
                  create: true,
                  prepopulate: o ,
                  module:'Emails',
                }
              });
            }, this),
            error: _.bind(function(o){
              console.log(o);
            }, this),
          });
        }
      },
      customReplyAllClicked: function(emailModel){
        var caseModel = this.get('parentModel'),
            caseId = caseModel.get('id'),
            emailType = emailModel.get('status');
        if(emailType == 'draft'){
          emailModel.set('isNotEmpty', true);
          //editing a draft
          //open the drawer with the email  as was when last saved
          app.drawer.open({
            layout:'compose',
            context:{
              create: false,
              model: emailModel,
              module: 'Emails',
            }
          },this);
        }else{
          var emailId = emailModel.get('id'),
              messageType = 'replyAll',//internal, reply, replyAll or new
              url = app.api.buildURL('Emails/getCustomCompose/'+messageType+'/'+caseId+'/'+emailId);
          //new reply pre-populate some information
          App.api.call('GET', url, '',{
            success: _.bind(function(o){
              app.drawer.open({
                layout:'compose',
                context:{
                  create: true,
                  prepopulate: o ,
                  module:'Emails',
                }
              });
            }, this),
            error: _.bind(function(o){
              console.log(o);
            }, this),
          });
        }
      },
      customReplyClicked: function(emailModel){
        var caseModel = this.get('parentModel'),
            caseId = caseModel.get('id'),
            emailType = emailModel.get('status');
        if(emailType == 'draft'){
          emailModel.set('isNotEmpty', true);
          //editing a draft
          //open the drawer with the email  as was when last saved
          app.drawer.open({
            layout:'compose',
            context:{
              create: false,
              model: emailModel,
              module: 'Emails',
            }
          },this);
        }else{
          var emailId = emailModel.get('id'),
              messageType = 'reply',//internal, reply, replyAll or new
              url = app.api.buildURL('Emails/getCustomCompose/'+messageType+'/'+caseId+'/'+emailId);
          //new reply pre-populate some information
          App.api.call('GET', url, '',{
            success: _.bind(function(o){
              app.drawer.open({
                layout:'compose',
                context:{
                  create: true,
                  prepopulate: o ,
                  module:'Emails',
                }
              });
            }, this),
            error: _.bind(function(o){
              console.log(o);
            }, this),
          });
        }
      }
    });
    

    As you can see the actions use a CustomCompose, which is the custom API that takes care of setting the necessary variables for the drawer to prepopulate with the necessary information

    in custom/modules/Emails/clients/base/api/getCustomComposeApi.php

    (I have some additional custom compose here for panel-top emails actions such as Internal Compose, and Custom Compose which are panel-top actions that are not specific to an email).

    <?php
    class getCustomComposeApi extends SugarApi
    {
      public function registerApiRest(){
        return array(
          'getCustomCompose' => array(
            // What type of HTTP request to match against, we support GET/PUT/POST/DELETE
            'reqType' => 'GET',
            // This is the path you are hoping to match, it also accepts wildcards of ? and <module>
            'path' => array('Emails', 'getCustomCompose', '?','?','?'),
            // These take elements from the path and use them to populate $args
            'pathVars' => array('','','type','caseId', 'emailId'),
            // This is the method name in this class that the url maps to
            'method' => 'getCustomCompose',
            // The shortHelp is vital, without it you will not see your endpoint in the /help
            'shortHelp' => 'Generates options for Email Reply button on subpanel in cases module',
            // The longHelp points to an HTML file and will be there on /help for people to expand and show
            'longHelp' => '',
          ),
        );
      }
    
      /**
       * Generate the Custom compose data package consumed by the quick compose screens.
       *
       * @param Array $data
       * @param $data['email_id'] email The BeanID for the Email we are replying to
       * @param $data['parent_id'] is assumed to be a Case ID
       * @param $type internal = send to Engineers, new = send to user, use case data likely no email to reply to, reply = copy original email .
       */
      function getCustomCompose($api, $args){
        global $current_user, $app_list_strings;
        $data = array();
        $data['email_id']= !empty($args['emailId'])?$args['emailId']:'';
        $data['parent_id']=$args['caseId'];
        $type = $args['type'];
        $toAddresses = array();
        $ccAddresses = array();
        //need to retrieve the full case bean, not all the bean values are pulled in the "bean"
        $case = BeanFactory::retrieveBean('Cases',$data['parent_id']);
        $parent_id = $case->id;
        $parent_type = 'Cases';
        if($type== 'new'){
          $GLOBALS['log']->debug("CustomCompose:: New");
          //NEW EMAIL to Customers
          //send the email TO all contacts on the case, eliminate duplicates
          //default in the from address, this is needed especially for cases from forms that don't have
          //any matching contacts
          $rel = 'contacts';
          $case->load_relationship($rel);
          foreach($case->$rel->getBeans() as $contact){
            $sea = BeanFactory::newBean('EmailAddresses');
            $email_addr = $sea->getPrimaryAddress($contact);
            $toAddresses[]=array(
              'email'=>$email_addr,
              'module'=> 'Contacts',
              'name'=>$contact->full_name
            );
          }
          //add the address from the case if valid
          //sugar should take care not to send duplicates 
          if(isset($case->case_from_addr_c) && filter_var($case->case_from_addr_c, FILTER_VALIDATE_EMAIL)){
            $toAddresses[]=array(
              'email'=>$case->case_from_addr_c,
              'module'=> '',
              'name'=>$case->case_from_addr_c
            );
          }
          //set up the BODY of the email as a copy of the case description
          $preBodyHTML = "<div><br><br><br> <hr></div>" . "<i><small>On: " . $case->date_entered . " " . $case->case_from_addr_c . " submitted the following Case:</small></i><br />";
          $order   = array("\r\n", "\n", "\r");
          $replace = '<br />';
          //default the body to the description of the case
          //in case we use this from panel-top
          $body = str_replace($order, $replace,$case->description);
          if(strpos($case->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
            //set the SUBJECT with the case macro valid for all compose types
            $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $case->name);
          }else{
             $subject = $case->name;
          }
          if(!preg_match('/^(re:)+/i', $subject)) {
            $subject = "RE: {$subject} ";
          }
        }elseif($type == 'internal'){
          $GLOBALS['log']->debug("CustomCompose:: internal");
           //INTERNAL EMAIL to Case Engineers
           //send the email TO all engineers on the case, eliminate duplicates
           $rel = 'cases_users_1';
           $case->load_relationship($rel);
           $toAddresses = array();
           foreach($case->$rel->getBeans() as $user){
             $sea = BeanFactory::newBean('EmailAddresses');
             $email_addr = $sea->getPrimaryAddress($user);
             if(!empty($email_addr)){
               $GLOBALS['log']->debug("add user to To address:" . $email_addr);
               $toAddresses[]=array(
                 'email'=>$email_addr,
                 'module'=> 'Users',
                 'name'=>$user->user_name
               );
             }
           }
           //get assigned user info
           if(!empty($case->assigned_user_id)){
             $auser = BeanFactory::retrieveBean('Users', $case->assigned_user_id);
             //add the assigned user to the To
             //the To cannot be empty, if there is no one the system will add the original customer
             $sea = BeanFactory::newBean('EmailAddresses');;
             $aemail_addr = $sea->getPrimaryAddress($auser);
             $GLOBALS['log']->debug("add user to To address:" . $aemail_addr);
             if(!empty($aemail_addr)){
               $toAddresses[]=array(
                 'email'=>$aemail_addr,
                 'module'=> 'Users',
                 'name'=>$auser->user_name
               );
             }
           }
           // add current user
           $cea = BeanFactory::newBean('EmailAddresses');;
           $cemail_addr = $cea->getPrimaryAddress($current_user);
           $GLOBALS['log']->debug("add user to To address:" . $cemail_addr);
           if(!empty($cemail_addr)){
              $toAddresses[]=array(
                'email'=>$cemail_addr,
                'module'=> 'Users',
                'name'=>$current_user->user_name
              );
           }
           if (empty($auser)) $auser = $current_user;
           //get primary contact info for body
           $contact = BeanFactory::retrieveBean('Contacts',$case->contact_id_c);
           //get Account info for body
           $account = BeanFactory::retrieveBean('Accounts',$case->account_id);
           $order   = array("\r\n", "\n", "\r");
           $replace = '<br />';
           $preBodyHTML =<<<BODY
    <pre>
    ================================================
         This is a posting from Technical Support
    ================================================
             Engineer: {$auser->first_name} {$auser->last_name}
                        speaking for...
             Customer: {$contact->full_name}
         Organization: {$account->name}
              Product: {$app_list_strings['case_product_dd'][$case->case_product_c]}
              Version: {$case->case_product_version_c}
             Platform: {$app_list_strings['product_platforms_list_DD'][$case->case_platform_c]}
        Date Received: {$case->date_entered}
    
    Technical Summary:
    
            {$case->case_summary_c}
    
          Description:
    
            {$case->description}
    ------------------------------------------------
    </pre>
    BODY;
          $body = '';
          //set the SUBJECT with the case macro valid for all compose types
          if(strpos($case->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
            //set the SUBJECT with the case macro valid for all compose types
            $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $case->name);
          }else{
             $subject = $case->name;
          }
          if(!preg_match('/^(re:)+/i', $subject)) {
            $subject = "RE: {$subject} ";
          }
        }elseif($type == 'reply' && !empty($data['email_id'])){
          $GLOBALS['log']->debug("CustomCompose:: as Reply to emailID " .$data['email_id']);
          //assume it's a reply message TO the original sender of the email in question
          //or a draft
          $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
          if($ie->type == 'draft'){
            //return the draft as is
            return;
          }else{
            //not a draft, composing a reply
            //set the recipient as the reply-to or from of the original message
            //unless it's from the queue
            if((!empty($ie->reply_to_email) && $ie->reply_to_email!=$case->case_queue_c) ||
               (!empty($ie->from_addr) && $ie->from_addr !=$case->case_queue_c)){
              $email_addr = !empty($ie->reply_to_email)?$ie->reply_to_email:$ie->from_addr;
            }else{
              $email_addr = $ie->to_addrs;
            }
            $GLOBALS['log']->debug('$email_addr = ' . $email_addr);
            $name = !empty($ie->reply_to_addr)?$ie->reply_to_addr:$email_addr;
            $GLOBALS['log']->debug('$name = ' . $name);
            if(!empty($email_addr)){
              $toAddresses[]=array(
                'email'=>$email_addr,
                'module'=> '',
                'name'=>$name,
              );
            }
            //include Cc'd addresses?
    
            //include Case Contacts
    /*
            $case = BeanFactory::retrieveBean('Cases',$data['parent_id']);
            $parent_id = $case->id;
            $parent_type = 'Cases';
            $rel = 'contacts';
            $case->load_relationship($rel);
            foreach($case->$rel->getBeans() as $contact){
              $sea = BeanFactory::newBean('EmailAddresses');;
              $email_addr = $sea->getPrimaryAddress($contact);
              $toAddresses[]=array(
                'email'=>$email_addr,
                'module'=> 'Contacts',
                'name'=>$contact->full_name
              );
            }
    */
            //use the BODY of the original message in the email
            $description = '';
            if(!empty($ie->description)){
              $description = $ie->description;
            }elseif(!empty($ie->description_html)){
              $description = $ie->description_html;
            }
            $GLOBALS['log']->debug('description = ' . $description);
            $preBodyHTML = " <div><br><br><br><hr></div>";
            $order   = array("\r\n", "\n", "\r");
            $replace = '<br />';
            $body = str_replace($order, $replace,$description);
            //the email should come FROM the queue, not the individual
            //we override the default in custom/modules/Emails/clients/base/api/sender/sender.js
            if(strpos($ie->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
              $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $ie->name);
            }else{
              $subject=$ie->name;
            }
            if(!preg_match('/^(re:)+/i', $subject)) {
              $subject = "RE: {$subject} ";
            }
          }
        }elseif($type == 'replyAll' && !empty($data['email_id'])){
          $GLOBALS['log']->debug("CustomCompose:: as Reply to emailID " .$data['email_id']);
          //assume it's a reply message TO the original sender of the email in question
          //or a draft
          $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
          if($ie->type == 'draft'){
            //return the draft as is
            return;
          }else{
            //not a draft, composing a reply
            //set the recipient as the reply-to or from of the original message
            $email_addr = !empty($ie->reply_to_email)?$ie->reply_to_email:$ie->from_addr;
            $GLOBALS['log']->debug('$email_addr = ' . $email_addr);
            $name = !empty($ie->reply_to_addr)?$ie->reply_to_addr:$email_addr;
            $GLOBALS['log']->debug('$name = ' . $name);
            if(!empty($email_addr)){
              $toAddresses[]=array(
                'email'=>$email_addr,
                'module'=> '',
                'name'=>$name,
              );
            }
    
            //include Cc
            $email_addr_cc = !empty($ie->cc_addrs)?$ie->cc_addrs:'';
            $GLOBALS['log']->debug('$email_addr_cc = ' . $email_addr_cc);
            $name = !empty($ie->cc_addrs)?$ie->cc_addrs:'';
            $GLOBALS['log']->debug('$name = ' . $name);
            if(!empty($email_addr_cc)){
              $ccAddresses[]=array(
                'email'=>$email_addr,
                'module'=> '',
                'name'=>$name,
              );
            }
    
    
            //use the BODY of the original message in the email
            $description = '';
            if(!empty($ie->description)){
              $description = $ie->description;
            }elseif(!empty($ie->description_html)){
              $description = $ie->description_html;
            }
            $GLOBALS['log']->debug('description = ' . $description);
            $preBodyHTML = " <div><br><br><br><hr></div>";
            $order   = array("\r\n", "\n", "\r");
            $replace = '<br />';
            $body = str_replace($order, $replace,$description);
            //the email should come FROM the queue, not the individual
            //we override the default in custom/modules/Emails/clients/base/api/sender/sender.js
            if(strpos($ie->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
              $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $ie->name);
            }else{
              $subject=$ie->name;
            }
            if(!preg_match('/^(re:)+/i', $subject)) {
              $subject = "RE: {$subject} ";
            }
          }
    
        }elseif($type == 'forward' && !empty($data['email_id'])){
          $GLOBALS['log']->debug("CustomCompose:: as Forward emailID " .$data['email_id']);
          $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
          if($ie->type == 'draft'){
            //return the draft as is
            return;
          }else{
            //not a draft, composing a forward
            //no recipient therefore set to current user or the system will default customer
            // add current user
            $cea = BeanFactory::newBean('EmailAddresses');;
            $cemail_addr = $cea->getPrimaryAddress($current_user);
            $GLOBALS['log']->debug("add user to To address:" . $cemail_addr);
            if(!empty($cemail_addr)){
               $toAddresses[]=array(
                 'email'=>$cemail_addr,
                 'module'=> 'Users',
                 'name'=>$current_user->user_name
               );
            }
            //use the BODY of the original message in the email
            $description = '';
            if(!empty($ie->description)){
              $description = $ie->description;
            }elseif(!empty($ie->description_html)){
              $description = $ie->description_html;
            }
            $GLOBALS['log']->debug('description = ' . $description);
            $ie = BeanFactory::retrieveBean('Emails',$data['email_id']);
    
            $preBodyHTML =<<<FWD
    <pre>
    ---------- Forwarded message ----------
    From: {$ie->from_addr}
    Date: {$ie->date_sent} (UTC)
    Subject: {$ie->name}
    To: {$ie->to_addrs}
    </pre>
    FWD;
            $order   = array("\r\n", "\n", "\r");
            $replace = '<br />';
            $body = str_replace($order, $replace,$description);
            //the email should come FROM the queue, not the individual
            //we override the default in custom/modules/Emails/clients/base/api/sender/sender.js
            if(strpos($ie->name, str_replace('%1',$case->case_number,$case->getEmailSubjectMacro()))===FALSE){
              $subject=str_replace('%1', $case->case_number, $case->getEmailSubjectMacro() . " ". $ie->name);
            }else{
              $subject=$ie->name;
            }
            if(!preg_match('/^(fwd:)+/i', $subject)) {
              $subject = "Fwd: {$subject} ";
            }
          }
    
        }
    
        //build the return the package
        $composePackage = array(
          'to_addresses' => $toAddresses,
          'cc_addresses' => $ccAddresses,
          'parent_type'      => 'Cases',
          'parent_id'        => $case->id,
          'parent_name'    => $case->name,
          'subject'  => $subject,
          'html_body'        => $preBodyHTML . $body,
          'body'             => $preBodyHTML . $body,
          'attachments'      => array(),
          //'email_id'       => (isset($email_id)?$email_id:''),
          //'email_config'   => $fromAddress,
        );
        //the compose needs some additional information to get the preview 
        if($type == 'reply') $composePackage['related'] = array('parent_id'=>$case->id, 'parent_type'=>'Cases');
        foreach ($composePackage as $key => $singleCompose)
        {
          if (is_string($singleCompose)) $composePackage[$key] = str_replace(" ", " ", from_html($singleCompose));
        }
        return ($composePackage);
      }
    }
    

    Note that we also override the sender of the email to be the queue - we don't send out Case emails from individuals but queues:

    custom/modules/Emails/clients/base/fields/sender/sender.js (I have the wrong path in the code comments above line 347)

    Note that it uses the "case_queue_c" field in Cases (a custom field) that is populated with the email address of the case queue in a logic hook when the Case is created (another adventure, see: https://community.sugarcrm.com/message/81897#comment-81897 )

    ({
        fieldTag: 'input.select2',
        initialize: function(options) {
            _.bindAll(this);
            app.view.Field.prototype.initialize.call(this, options);
            this.endpoint = this.def.endpoint;
        },
        _render: function() {
            var result = app.view.Field.prototype._render.call(this);
            if (this.tplName === 'edit') {
                var action = (this.endpoint.action) ? this.endpoint.action : null,
                    attributes = (this.endpoint.attributes) ? this.endpoint.attributes : null,
                    params = (this.endpoint.params) ? this.endpoint.params : null,
                    myURL = app.api.buildURL(this.endpoint.module, action, attributes, params);
                app.api.call('GET', myURL, null, {
                    success: this.populateValues,
                    error:   function(error) {
                        app.alert.show('server-error', {
                            level: 'error',
                            messages: 'ERR_GENERIC_SERVER_ERROR'
                        });
                        app.error.handleHttpError(error);
                    }
                });
            }
            return result;
        },
        populateValues: function(results) {
            var self = this,
                defaultResult,
                defaultValue = {},
                parentMod = this.context.parent,
                parentModule = parentMod.get('module'),
                queue = ('('+parentMod.get('model').get('case_queue_c')+')').replace(/[.*+?^${}()|[\]\\]/g, "\\$&");;
            if (this.disposed === true) {
                return; //if field is already disposed, bail out
            }
            if (!_.isEmpty(results)) {
                if(parentModule == 'Cases' && typeof(queue)!= 'undefined'){
                  var pattern = new RegExp('.*'+queue+'.*');
                  defaultResult = _.find(results, function(result) {
                    return result.display.match(pattern);
                  });
                }else{
                  defaultResult = _.find(results, function(result) {
                    return result.default;
                  });
                }
                defaultValue = (defaultResult) ? defaultResult : results[0];
                if (!this.model.has(this.name)) {
                    this.model.set(this.name, defaultValue.id);
                }
            }
            var format = function(item) {
                return item.display;
            };
            this.$(this.fieldTag).select2({
                data:{ results: results, text: 'display' },
                formatSelection: format,
                formatResult: format,
                width: '100%',
                placeholder: app.lang.get('LBL_SELECT_FROM_SENDER', this.module),
                initSelection: function(el, callback) {
                    if (!_.isEmpty(defaultValue)) {
                          callback(defaultValue);
                    }
                }
            }).on("change", function(e) {
                if (self.model.get(self.name) !== e.val) {
                    self.model.set(self.name, e.val, {silent: true});
                }
            });
    
            this.$(".select2-container").addClass("tleft");
        },
    
      /**
       * {@inheritdoc}
       *
       * We need this empty so it won't affect refresh the select2 plugin
       */
      bindDomChange: function() {
      }
    })
    

    HTH, comments welcome.
    FrancescaS

  • Question Regarding : "In

    custom/modules/Emails/clients/base/views/<your cases subpanel view>/<your cases subpanel view>.php

    I added three custom email actions: reply, reply all and forward (opendetails ia a custom view of the email thread)

    They behave as you would expect in an email client."

    I do not have directory so I am assuming that I create it. Does the name of the file matter or is it just that it needs to be the same as the directory above it?

    So will this php file by itself edit the view so after I do a rebuild I will see buttons in the subpanel row for emails?  If you can provide screenshot of finished result that would help me visualize what does what.

  • I put the suggested code snippets into my project and did a quick rebuild, but I don't see any changes?

    As you can tell I am still new to this new way of creating views.  I'm even more confused at which file you know sugar is using.  How do you know these things!?

  • My requirements have changed slightly or rather I misunderstood them.  I am to create a new module to save the cc emails related to each case.  Then in our workflows do a select from the table generated by the new module and append the cc information with that result set. Your answer is accurate to the original question so I am marking this as the answer and If I need more help i'll make another post on this forum.  Thank you for your help!

  • You might want to ponder that request for a bit, I don't see why you would need a new module to save the Cc's. The information is already there, the emails should be linked to the people Cc'd. Take a look at the emails_email_addr_rel table, which is related to emails and to email_addresses. looking at emails.parent_type = 'Cases' should give you emails that are related to cases and address_type = 'cc' will give you all those who are cc'd.

    The query would look something like this:

    select eabr.bean_module, eabr.bean_id
    from cases c
    join emails e on e.parent_id = c.id and e.parent_type = 'Cases'
    join emails_email_addr_rel eear on eear.email_id = e.id and address_type = 'cc'
    join email_addresses ea on ea.id = eear.email_address_id
    join email_addr_bean_rel eabr on eabr.email_address_id = ea.id
    

    where

    eabr.bean_module could be Contacts, Users, Leads, Accounts - basically any module with an email address on it and eabr.bean_id the id.

    You could restrict your search by module with a where clause and/or join the relevant table for the appropriate information.

    For example for contacts first and last name:

    select con.first_name, con.last_name
    from cases c
    join emails e on e.parent_id = c.id and e.parent_type = 'Cases'
    join emails_email_addr_rel eear on eear.email_id = e.id and address_type = 'cc'
    join email_addresses ea on ea.id = eear.email_address_id
    join email_addr_bean_rel eabr on eabr.email_address_id = ea.id and eabr.bean_module = 'Contacts'
    join contacts con on con.id = eabr.bean_id
    

    HTH,
    FrancescaS

  • That is very helpful and also raises more questions about how I am supposed to go about solving my problem. So if the information I want is already there and all I need to do is write the query as stated in your comment I guess my next question would be how would I ensure that every email sent in my companies workflow about a case is appended with the correct CC addresses?

    My other dev here and I looked at the api to do this. Specifically the InboundEmail.php file in ./modules/InboundEmails/InboundEmails.php  .  Could I extend this in the custom directory for the specific functions that send emails? 

    Thank you again for all your help thus far.

  • I have not found a way to extend InboundEmails in a truly upgrade safe manner.

    I am not sure I understand your requirement fully. Are you sending emails from an external source with Cc's that need to be recorded in your sugar instance as Cases?

    In general, if you send an email and copy the Cases queue (in the To or Cc) you should have all the information in your database, including any Cc's included in the email you sent.

Reply Children
  • So problem is when a customer emails us it will create a case.  The workflow will not keep track of who is CC'ed in emails. So when the helpdesk agent responds they manually put in the same people that were CC'ed from the customer. This is a problem because the Customer will CC people so that they are also aware of the status of issues.  The helpdesk agent could forget/not notice/misspell an email then that person is left out of the loop.

    So In order to solve this issue I need to know where the emails are sent out from and how to extend that with appropriate information so that it CC's people automatically and there will be no user error when sending the email.  Thanks to your sql statement I have the info I need which would be all the CC'ed people in a case. Now I just need to know how to add these people to every email sent out by sugar in regards to a specific case.

    Does that make sense?Francesca Shiekh

  • That is the problem I solved by adding the "Reply All" option to the rowactions in the email subpanel in Cases (the crazy code I provided above). It takes all the Cc's from the original email and adds them to the email being composed by the helpdesk agent who is responding to the customer (just like a Reply All on a regular email client). They then also have the option of removing/adding more recipients/cc's as needed.

  • Well I definitely need to review that code for the next couple of hours.

    So your solution will create a row action button in the emails subpanel for cases, but this solution will only update that view that appears when the plus button is created.  I need it to work with the notes module in a case.  So when the helpdesk agent creates a note it will send an alert(email) depending on what the note is.  This email currently doesn't have any of the CC'ed people on it from previous emails.

    Sorry I didn't say this before.  I'm asking more questions as I go.

    So this would explain the need for a new module so that I can go through the admin panel and tell the workflow to send an email to all the people in the cc_email module.

    Do you think this will work or did I make more questions?Francesca Shiekh

  • Gosh, looks like you have a lot of customizations in your Cases module, the Public Note is not an out-of-the-box.

    The best I can do is tell you how I would approach it, but that doesn't make it the right way or the only way... so take it with a grain of salt.

    My basic attitude is: don't duplicate data unless ABSOLUTELY necessary.

    In your case I don't think it's necessary.

    You must have an email compose related to that Public Note somewhere.

    You should be able to pull all email addresses from the case related emails and add them to the Cc. If you don't add the address_type on the query below you'll get ALL addresses.

    select distinct(ea.email_address)
    from cases c
    join emails e on e.parent_id = c.id and e.parent_type = 'Cases'
    join emails_email_addr_rel eear on eear.email_id = e.id and address_type = 'cc'
    join email_addresses ea on ea.id = eear.email_address_id
    join email_addr_bean_rel eabr on eabr.email_address_id = ea.id
    where cases.id = $case_id
    

    Without knowing how your Public Note emails go out it's hard for me to help you with the code.

    If you are using an email drawer you should have an app.drawer.open for module: Email somewhere, and you should be able to pass some "prepopulate" data, as I do in the ReplyAll from my customCompose, the biggest difference is that I'm replying to a specific email and therefore get all recipients from that email, you instead have to find all the addresses included in any of your Case's emails and therefore need the query above to get them (and depending on how many emails you have on that case that could be quite a few).

    HTH

    Good Luck, you sure jumped in on the deep end!

  • Hehe yes I have my work cut out for me. Thank you for your help. 

  • So I believe that the code for producing the email in ./include/workflow/alert_util.php which appears to be a stock class from sugar.  So how l would I go about extending on that?  Or maybe just file this problem away for now and come back to it later when I have more thoughts on the subject.  

  • Looks like you are using the Workflow module to generate the email and the service rep doesn't even see the email...

    The workflow will have a condition like email the contacts related to the case or some such (I don't use Workflow so I'm not too familiar with it - I prefer logic_hooks and the Professional version I'm on won't have Workflow in the near future)...

    Using Workflow you need to have all your Cc's related to the Case - I forget if the stock Cases allows for multiple contacts on a case or not, but if the Cc is an address not related to a Contact you won't be able to pull it...

    One option: replace the workflow with a logic hook, when the note is saved trigger a logic hook to generate an email and include the Cc's by using the query we discussed earlier. There are a few examples out there of how to send emails from sugarCRM using code.

    Read up on logic hooks here:

    https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_7.6/Logic_Hooks/