mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	feat: Ability to customise the email sender name [CW-1629] (#7345)
This commit is contained in:
		| @@ -441,7 +441,7 @@ GEM | ||||
|     mini_magick (4.12.0) | ||||
|     mini_mime (1.1.2) | ||||
|     mini_portile2 (2.8.2) | ||||
|     minitest (5.18.0) | ||||
|     minitest (5.18.1) | ||||
|     mock_redis (0.36.0) | ||||
|       ruby2_keywords | ||||
|     msgpack (1.7.0) | ||||
| @@ -450,7 +450,7 @@ GEM | ||||
|     multipart-post (2.3.0) | ||||
|     net-http-persistent (4.0.2) | ||||
|       connection_pool (~> 2.2) | ||||
|     net-imap (0.3.4) | ||||
|     net-imap (0.3.6) | ||||
|       date | ||||
|       net-protocol | ||||
|     net-pop (0.1.2) | ||||
| @@ -525,7 +525,7 @@ GEM | ||||
|     pundit (2.3.0) | ||||
|       activesupport (>= 3.0.0) | ||||
|     raabro (1.4.0) | ||||
|     racc (1.7.0) | ||||
|     racc (1.7.1) | ||||
|     rack (2.2.7) | ||||
|     rack-attack (6.6.1) | ||||
|       rack (>= 1.0, < 3) | ||||
| @@ -731,7 +731,7 @@ GEM | ||||
|     time_diff (0.3.0) | ||||
|       activesupport | ||||
|       i18n | ||||
|     timeout (0.3.2) | ||||
|     timeout (0.4.0) | ||||
|     trailblazer-option (0.1.2) | ||||
|     twilio-ruby (5.77.0) | ||||
|       faraday (>= 0.9, < 3.0) | ||||
|   | ||||
| @@ -124,7 +124,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController | ||||
|   def inbox_attributes | ||||
|     [:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled, | ||||
|      :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved, | ||||
|      :lock_to_single_conversation, :portal_id] | ||||
|      :lock_to_single_conversation, :portal_id, :sender_name_type, :business_name] | ||||
|   end | ||||
|  | ||||
|   def permitted_params(channel_attributes = []) | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <template> | ||||
|   <div class="row settings--section"> | ||||
|   <div class="row settings--section" :class="{ border: showBorder }"> | ||||
|     <div class="medium-4 small-12 title--section"> | ||||
|       <p class="sub-block-title"> | ||||
|       <p v-if="title" class="sub-block-title"> | ||||
|         {{ title }} | ||||
|       </p> | ||||
|       <p class="sub-head"> | ||||
|         <slot name="subTitle"> | ||||
|         <slot v-if="subTitle" name="subTitle"> | ||||
|           {{ subTitle }} | ||||
|         </slot> | ||||
|       </p> | ||||
| @@ -25,11 +25,15 @@ export default { | ||||
|   props: { | ||||
|     title: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|       default: '', | ||||
|     }, | ||||
|     subTitle: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|       default: '', | ||||
|     }, | ||||
|     showBorder: { | ||||
|       type: Boolean, | ||||
|       default: true, | ||||
|     }, | ||||
|     note: { | ||||
|       type: String, | ||||
| @@ -43,9 +47,13 @@ export default { | ||||
| @import '~dashboard/assets/scss/variables'; | ||||
|  | ||||
| .settings--section { | ||||
|   border-bottom: 1px solid $color-border; | ||||
|   display: flex; | ||||
|   padding: $space-normal $space-normal $space-normal 0; | ||||
|   padding: 0 $space-normal $space-normal 0; | ||||
|  | ||||
|   &.border { | ||||
|     padding-top: $space-normal; | ||||
|     border-bottom: 1px solid $color-border; | ||||
|   } | ||||
|  | ||||
|   .sub-block-title { | ||||
|     color: $color-woot; | ||||
|   | ||||
| @@ -13,9 +13,10 @@ | ||||
|     <div class="content-wrap"> | ||||
|       {{ content }} | ||||
|     </div> | ||||
|     <div class="image-wrap"> | ||||
|     <div v-if="src" class="image-wrap"> | ||||
|       <img :src="src" class="image" :class="{ activeimage: active }" /> | ||||
|     </div> | ||||
|     <slot v-else /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -391,6 +391,25 @@ | ||||
|         "ENABLED": "Enabled", | ||||
|         "DISABLED": "Disabled" | ||||
|       }, | ||||
|       "SENDER_NAME_SECTION": { | ||||
|         "TITLE": "Sender name", | ||||
|         "SUB_TEXT": "Select the name shown to the your customer when they receive emails from your agents.", | ||||
|         "FOR_EG": "For eg:", | ||||
|         "FRIENDLY": { | ||||
|           "TITLE": "Friendly", | ||||
|           "FROM": "from", | ||||
|           "SUBTITLE": "Add the name of the agent who sent the reply in the sender name to make it friendly." | ||||
|         }, | ||||
|         "PROFESSIONAL": { | ||||
|           "TITLE": "Professional", | ||||
|           "SUBTITLE": "Use only the configured business name as the sender name in the email header." | ||||
|         }, | ||||
|         "BUSINESS_NAME": { | ||||
|           "BUTTON_TEXT": "+ Configure your business name", | ||||
|           "PLACEHOLDER": "Enter your business name", | ||||
|           "SAVE_BUTTON_TEXT": "Save" | ||||
|         } | ||||
|       }, | ||||
|       "ALLOW_MESSAGES_AFTER_RESOLVED": { | ||||
|         "ENABLED": "Enabled", | ||||
|         "DISABLED": "Disabled" | ||||
| @@ -454,7 +473,9 @@ | ||||
|       "ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation", | ||||
|       "AUTO_ASSIGNMENT": "Enable auto assignment", | ||||
|       "ENABLE_CSAT": "Enable CSAT", | ||||
|       "SENDER_NAME_SECTION": "Enable Agent Name in Email", | ||||
|       "ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation", | ||||
|       "SENDER_NAME_SECTION_TEXT": "Enable/Disable showing Agent's name in email, if disabled it will show business name", | ||||
|       "ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email", | ||||
|       "ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.", | ||||
|       "LOCK_TO_SINGLE_CONVERSATION": "Lock to single conversation", | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
|       <settings-section | ||||
|         :title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_TITLE')" | ||||
|         :sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_SUB_TEXT')" | ||||
|         :show-border="false" | ||||
|       > | ||||
|         <woot-avatar-uploader | ||||
|           :label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL')" | ||||
| @@ -258,7 +259,7 @@ | ||||
|             }} | ||||
|           </p> | ||||
|         </label> | ||||
|         <div class="medium-9 settings-item settings-item"> | ||||
|         <div class="medium-9 settings-item"> | ||||
|           <label> | ||||
|             {{ $t('INBOX_MGMT.HELP_CENTER.LABEL') }} | ||||
|           </label> | ||||
| @@ -313,7 +314,7 @@ | ||||
|             {{ $t('INBOX_MGMT.FEATURES.DISPLAY_FILE_PICKER') }} | ||||
|           </label> | ||||
|         </div> | ||||
|         <div v-if="isAWebWidgetInbox" class="settings-item settings-item"> | ||||
|         <div v-if="isAWebWidgetInbox" class="settings-item"> | ||||
|           <input | ||||
|             v-model="selectedFeatureFlags" | ||||
|             type="checkbox" | ||||
| @@ -324,7 +325,7 @@ | ||||
|             {{ $t('INBOX_MGMT.FEATURES.DISPLAY_EMOJI_PICKER') }} | ||||
|           </label> | ||||
|         </div> | ||||
|         <div v-if="isAWebWidgetInbox" class="settings-item settings-item"> | ||||
|         <div v-if="isAWebWidgetInbox" class="settings-item"> | ||||
|           <input | ||||
|             v-model="selectedFeatureFlags" | ||||
|             type="checkbox" | ||||
| @@ -335,7 +336,7 @@ | ||||
|             {{ $t('INBOX_MGMT.FEATURES.ALLOW_END_CONVERSATION') }} | ||||
|           </label> | ||||
|         </div> | ||||
|         <div v-if="isAWebWidgetInbox" class="settings-item settings-item"> | ||||
|         <div v-if="isAWebWidgetInbox" class="settings-item"> | ||||
|           <input | ||||
|             v-model="selectedFeatureFlags" | ||||
|             type="checkbox" | ||||
| @@ -346,7 +347,54 @@ | ||||
|             {{ $t('INBOX_MGMT.FEATURES.USE_INBOX_AVATAR_FOR_BOT') }} | ||||
|           </label> | ||||
|         </div> | ||||
|  | ||||
|       </settings-section> | ||||
|       <settings-section | ||||
|         v-if="isAWebWidgetInbox || isAnEmailChannel" | ||||
|         :title="$t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.TITLE')" | ||||
|         :sub-title="$t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.SUB_TEXT')" | ||||
|         :show-border="false" | ||||
|       > | ||||
|         <div class="medium-9 settings-item"> | ||||
|           <sender-name-example-preview | ||||
|             :sender-name-type="senderNameType" | ||||
|             :business-name="businessName" | ||||
|             @update="toggleSenderNameType" | ||||
|           /> | ||||
|           <div class="business-section"> | ||||
|             <woot-button | ||||
|               variant="clear" | ||||
|               color-scheme="primary" | ||||
|               @click="onClickShowBusinessNameInput" | ||||
|             > | ||||
|               {{ | ||||
|                 $t( | ||||
|                   'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.BUSINESS_NAME.BUTTON_TEXT' | ||||
|                 ) | ||||
|               }} | ||||
|             </woot-button> | ||||
|             <div v-if="showBusinessNameInput" class="business-name-input"> | ||||
|               <input | ||||
|                 ref="businessNameInput" | ||||
|                 v-model="businessName" | ||||
|                 :placeholder=" | ||||
|                   $t( | ||||
|                     'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.BUSINESS_NAME.PLACEHOLDER' | ||||
|                   ) | ||||
|                 " | ||||
|                 type="text" | ||||
|               /> | ||||
|               <woot-button color-scheme="primary" @click="updateInbox"> | ||||
|                 {{ | ||||
|                   $t( | ||||
|                     'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.BUSINESS_NAME.SAVE_BUTTON_TEXT' | ||||
|                   ) | ||||
|                 }} | ||||
|               </woot-button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </settings-section> | ||||
|       <settings-section :show-border="false"> | ||||
|         <woot-submit-button | ||||
|           v-if="isAPIInbox" | ||||
|           type="submit" | ||||
| @@ -405,6 +453,7 @@ import CollaboratorsPage from './settingsPage/CollaboratorsPage'; | ||||
| import WidgetBuilder from './WidgetBuilder'; | ||||
| import BotConfiguration from './components/BotConfiguration'; | ||||
| import { FEATURE_FLAGS } from '../../../../featureFlags'; | ||||
| import SenderNameExamplePreview from './components/SenderNameExamplePreview'; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
| @@ -418,6 +467,7 @@ export default { | ||||
|     SettingsSection, | ||||
|     WeeklyAvailability, | ||||
|     WidgetBuilder, | ||||
|     SenderNameExamplePreview, | ||||
|   }, | ||||
|   mixins: [alertMixin, configMixin, inboxMixin], | ||||
|   data() { | ||||
| @@ -429,6 +479,8 @@ export default { | ||||
|       greetingMessage: '', | ||||
|       emailCollectEnabled: false, | ||||
|       csatSurveyEnabled: false, | ||||
|       senderNameType: 'friendly', | ||||
|       businessName: '', | ||||
|       locktoSingleConversation: false, | ||||
|       allowMessagesAfterResolved: true, | ||||
|       continuityViaEmail: true, | ||||
| @@ -441,6 +493,7 @@ export default { | ||||
|       replyTime: '', | ||||
|       selectedTabIndex: 0, | ||||
|       selectedPortalSlug: '', | ||||
|       showBusinessNameInput: false, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
| @@ -621,6 +674,8 @@ export default { | ||||
|         this.greetingMessage = this.inbox.greeting_message || ''; | ||||
|         this.emailCollectEnabled = this.inbox.enable_email_collect; | ||||
|         this.csatSurveyEnabled = this.inbox.csat_survey_enabled; | ||||
|         this.senderNameType = this.inbox.sender_name_type; | ||||
|         this.businessName = this.inbox.business_name; | ||||
|         this.allowMessagesAfterResolved = this.inbox.allow_messages_after_resolved; | ||||
|         this.continuityViaEmail = this.inbox.continuity_via_email; | ||||
|         this.channelWebsiteUrl = this.inbox.website_url; | ||||
| @@ -650,6 +705,8 @@ export default { | ||||
|               ).id | ||||
|             : null, | ||||
|           lock_to_single_conversation: this.locktoSingleConversation, | ||||
|           sender_name_type: this.senderNameType, | ||||
|           business_name: this.businessName || null, | ||||
|           channel: { | ||||
|             widget_color: this.inbox.widget_color, | ||||
|             website_url: this.channelWebsiteUrl, | ||||
| @@ -694,6 +751,17 @@ export default { | ||||
|         ); | ||||
|       } | ||||
|     }, | ||||
|     toggleSenderNameType(key) { | ||||
|       this.senderNameType = key; | ||||
|     }, | ||||
|     onClickShowBusinessNameInput() { | ||||
|       this.showBusinessNameInput = !this.showBusinessNameInput; | ||||
|       if (this.showBusinessNameInput) { | ||||
|         this.$nextTick(() => { | ||||
|           this.$refs.businessNameInput.focus(); | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   validations: { | ||||
|     webhookUrl: { | ||||
| @@ -717,12 +785,6 @@ export default { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .settings--content { | ||||
|     div:last-child { | ||||
|       border-bottom: 0; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .tabs { | ||||
|     padding: 0; | ||||
|     margin-bottom: -1px; | ||||
| @@ -742,4 +804,22 @@ export default { | ||||
|     padding-bottom: var(--space-smaller); | ||||
|   } | ||||
| } | ||||
|  | ||||
| .business-section { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: flex-start; | ||||
|   gap: var(--space-small); | ||||
|   margin-top: var(--space-small); | ||||
|  | ||||
|   .business-name-input { | ||||
|     display: flex; | ||||
|     gap: var(--space-small); | ||||
|     width: 80%; | ||||
|  | ||||
|     input { | ||||
|       margin-bottom: 0; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -0,0 +1,161 @@ | ||||
| <template> | ||||
|   <div class="sender-name--preview-container"> | ||||
|     <button | ||||
|       v-for="keyOption in senderNameKeyOptions" | ||||
|       :key="keyOption.key" | ||||
|       class="preview-button" | ||||
|       @click="toggleSenderNameType(keyOption.key)" | ||||
|     > | ||||
|       <preview-card | ||||
|         :heading="keyOption.heading" | ||||
|         :content="keyOption.content" | ||||
|         :active="keyOption.key === senderNameType" | ||||
|       > | ||||
|         <div class="sender-name--preview-content"> | ||||
|           <span class="text"> | ||||
|             {{ $t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FOR_EG') }} | ||||
|           </span> | ||||
|           <div class="sender-name--preview"> | ||||
|             <thumbnail :username="userName(keyOption)" size="32px" /> | ||||
|             <div class="preview-card--content"> | ||||
|               <div class="sender-name--preview"> | ||||
|                 <span v-if="isKeyOptionFriendly(keyOption.key)" class="name"> | ||||
|                   {{ keyOption.preview.senderName }} | ||||
|                 </span> | ||||
|                 <span v-if="isKeyOptionFriendly(keyOption.key)" class="text"> | ||||
|                   {{ $t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FRIENDLY.FROM') }} | ||||
|                 </span> | ||||
|                 <span class="name text-truncate"> | ||||
|                   {{ businessName || keyOption.preview.businessName }} | ||||
|                 </span> | ||||
|               </div> | ||||
|               <span class="text">{{ keyOption.preview.email }}</span> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </preview-card> | ||||
|     </button> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import PreviewCard from 'dashboard/components/ui/PreviewCard'; | ||||
| import Thumbnail from 'dashboard/components/widgets/Thumbnail'; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     PreviewCard, | ||||
|     Thumbnail, | ||||
|   }, | ||||
|   props: { | ||||
|     senderNameType: { | ||||
|       type: String, | ||||
|       default: 'friendly', | ||||
|     }, | ||||
|     businessName: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       senderNameKeyOptions: [ | ||||
|         { | ||||
|           key: 'friendly', | ||||
|           heading: this.$t( | ||||
|             'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FRIENDLY.TITLE' | ||||
|           ), | ||||
|           content: this.$t( | ||||
|             'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FRIENDLY.SUBTITLE' | ||||
|           ), | ||||
|           preview: { | ||||
|             senderName: 'Smith', | ||||
|             businessName: 'Chatwoot', | ||||
|             email: '<support@yourbusiness.com>', | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           key: 'professional', | ||||
|           heading: this.$t( | ||||
|             'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.PROFESSIONAL.TITLE' | ||||
|           ), | ||||
|           content: this.$t( | ||||
|             'INBOX_MGMT.EDIT.SENDER_NAME_SECTION.PROFESSIONAL.SUBTITLE' | ||||
|           ), | ||||
|           preview: { | ||||
|             senderName: '', | ||||
|             businessName: 'Chatwoot', | ||||
|             email: '<support@yourbusiness.com>', | ||||
|           }, | ||||
|         }, | ||||
|       ], | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     isKeyOptionFriendly(key) { | ||||
|       return key === 'friendly'; | ||||
|     }, | ||||
|     userName(keyOption) { | ||||
|       return this.isKeyOptionFriendly(keyOption.key) | ||||
|         ? keyOption.preview.senderName | ||||
|         : keyOption.preview.businessName; | ||||
|     }, | ||||
|     toggleSenderNameType(key) { | ||||
|       this.$emit('update', key); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .sender-name--preview-container { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   align-items: center; | ||||
|   gap: var(--space-normal); | ||||
|  | ||||
|   .sender-name--preview-content { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: flex-start; | ||||
|     padding: var(--space-slab); | ||||
|     gap: var(--space-small); | ||||
|   } | ||||
| } | ||||
|  | ||||
| .preview-button { | ||||
|   color: var(--s-700); | ||||
|  | ||||
|   .text { | ||||
|     font-size: var(--font-size-mini); | ||||
|   } | ||||
|  | ||||
|   .sender-name--preview { | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     align-items: center; | ||||
|     gap: var(--space-small); | ||||
|  | ||||
|     .preview-card--content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: flex-start; | ||||
|       gap: var(--space-smaller); | ||||
|  | ||||
|       .sender-name--preview { | ||||
|         align-items: center; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: var(--space-micro); | ||||
|         max-width: 18rem; | ||||
|  | ||||
|         .name { | ||||
|           font-size: var(--font-size-mini); | ||||
|           font-weight: var(--font-weight-bold); | ||||
|           line-height: 1.2; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @@ -390,6 +390,7 @@ export const inboxes = [ | ||||
|     working_hours_enabled: false, | ||||
|     enable_email_collect: true, | ||||
|     csat_survey_enabled: true, | ||||
|     sender_name_type: 0, | ||||
|     enable_auto_assignment: true, | ||||
|     out_of_office_message: | ||||
|       'We are unavailable at the moment. Leave a message we will respond once we are back.', | ||||
|   | ||||
| @@ -79,14 +79,31 @@ class ConversationReplyMailer < ApplicationMailer | ||||
|     @conversation.messages.chat.where.not(message_type: :incoming)&.last | ||||
|   end | ||||
|  | ||||
|   def sender_name | ||||
|     @sender_name ||= current_message&.sender&.available_name || @agent&.available_name || 'Notifications' | ||||
|   def sender_name(sender_email) | ||||
|     if @inbox.friendly? | ||||
|       I18n.t('conversations.reply.email.header.friendly_name', sender_name: custom_sender_name, business_name: business_name, | ||||
|                                                                from_email: sender_email) | ||||
|     else | ||||
|       I18n.t('conversations.reply.email.header.professional_name', business_name: business_name, from_email: sender_email) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def current_message | ||||
|     @message || @conversation.messages.outgoing.last | ||||
|   end | ||||
|  | ||||
|   def custom_sender_name | ||||
|     current_message&.sender&.available_name || @agent&.available_name || 'Notifications' | ||||
|   end | ||||
|  | ||||
|   def business_name | ||||
|     @inbox.business_name || @inbox.name | ||||
|   end | ||||
|  | ||||
|   def from_email | ||||
|     should_use_conversation_email_address? ? parse_email(@account.support_email) : parse_email(inbox_from_email_address) | ||||
|   end | ||||
|  | ||||
|   def mail_subject | ||||
|     subject = @conversation.additional_attributes['mail_subject'] | ||||
|     return "[##{@conversation.display_id}] #{I18n.t('conversations.reply.email_subject')}" if subject.nil? | ||||
| @@ -101,26 +118,18 @@ class ConversationReplyMailer < ApplicationMailer | ||||
|  | ||||
|   def reply_email | ||||
|     if should_use_conversation_email_address? | ||||
|       I18n.t('conversations.reply.email.header.reply_with_name', assignee_name: sender_name, inbox_name: @inbox.name, | ||||
|                                                                  reply_email: "#{@conversation.uuid}@#{@account.inbound_email_domain}") | ||||
|       sender_name("reply+#{@conversation.uuid}@#{@account.inbound_email_domain}") | ||||
|     else | ||||
|       @inbox.email_address || @agent&.email | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def from_email_with_name | ||||
|     if should_use_conversation_email_address? | ||||
|       I18n.t('conversations.reply.email.header.from_with_name', assignee_name: sender_name, inbox_name: @inbox.name, | ||||
|                                                                 from_email: parse_email(@account.support_email)) | ||||
|     else | ||||
|       I18n.t('conversations.reply.email.header.from_with_name', assignee_name: sender_name, inbox_name: @inbox.name, | ||||
|                                                                 from_email: parse_email(inbox_from_email_address)) | ||||
|     end | ||||
|     sender_name(from_email) | ||||
|   end | ||||
|  | ||||
|   def channel_email_with_name | ||||
|     I18n.t('conversations.reply.channel_email.header.reply_with_name', assignee_name: sender_name, inbox_name: @inbox.name, | ||||
|                                                                        from_email: @channel.email) | ||||
|     sender_name(@channel.email) | ||||
|   end | ||||
|  | ||||
|   def parse_email(email_string) | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #  id                            :integer          not null, primary key | ||||
| #  allow_messages_after_resolved :boolean          default(TRUE) | ||||
| #  auto_assignment_config        :jsonb | ||||
| #  business_name                 :string | ||||
| #  channel_type                  :string | ||||
| #  csat_survey_enabled           :boolean          default(FALSE) | ||||
| #  email_address                 :string | ||||
| @@ -17,6 +18,7 @@ | ||||
| #  lock_to_single_conversation   :boolean          default(FALSE), not null | ||||
| #  name                          :string           not null | ||||
| #  out_of_office_message         :string | ||||
| #  sender_name_type              :integer          default("friendly"), not null | ||||
| #  timezone                      :string           default("UTC") | ||||
| #  working_hours_enabled         :boolean          default(FALSE) | ||||
| #  created_at                    :datetime         not null | ||||
| @@ -69,6 +71,8 @@ class Inbox < ApplicationRecord | ||||
|   has_many :webhooks, dependent: :destroy_async | ||||
|   has_many :hooks, dependent: :destroy_async, class_name: 'Integrations::Hook' | ||||
|  | ||||
|   enum sender_name_type: { friendly: 0, professional: 1 } | ||||
|  | ||||
|   after_destroy :delete_round_robin_agents | ||||
|  | ||||
|   scope :order_by_name, -> { order('lower(name) ASC') } | ||||
|   | ||||
| @@ -16,6 +16,8 @@ json.timezone resource.timezone | ||||
| json.callback_webhook_url resource.callback_webhook_url | ||||
| json.allow_messages_after_resolved resource.allow_messages_after_resolved | ||||
| json.lock_to_single_conversation resource.lock_to_single_conversation | ||||
| json.sender_name_type resource.sender_name_type | ||||
| json.business_name resource.business_name | ||||
|  | ||||
| if resource.portal.present? | ||||
|   json.help_center do | ||||
|   | ||||
| @@ -160,6 +160,8 @@ en: | ||||
|         header: | ||||
|           from_with_name: "%{assignee_name} from %{inbox_name} <%{from_email}>" | ||||
|           reply_with_name: "%{assignee_name} from %{inbox_name} <reply+%{reply_email}>" | ||||
|           friendly_name: "%{sender_name} from %{business_name} <%{from_email}>" | ||||
|           professional_name: "%{business_name} <%{from_email}>" | ||||
|       channel_email: | ||||
|         header: | ||||
|           reply_with_name: "%{assignee_name} from %{inbox_name} <%{from_email}>" | ||||
|   | ||||
| @@ -0,0 +1,5 @@ | ||||
| class AddCustomSenderNameToggle < ActiveRecord::Migration[7.0] | ||||
|   def change | ||||
|     add_column :inboxes, :sender_name_type, :integer, default: 0, null: false | ||||
|   end | ||||
| end | ||||
							
								
								
									
										5
									
								
								db/migrate/20230614044633_add_sender_name_to_in.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								db/migrate/20230614044633_add_sender_name_to_in.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| class AddSenderNameToIn < ActiveRecord::Migration[7.0] | ||||
|   def change | ||||
|     add_column :inboxes, :business_name, :string, null: true | ||||
|   end | ||||
| end | ||||
| @@ -584,6 +584,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_20_212340) do | ||||
|     t.jsonb "auto_assignment_config", default: {} | ||||
|     t.boolean "lock_to_single_conversation", default: false, null: false | ||||
|     t.bigint "portal_id" | ||||
|     t.string "business_name" | ||||
|     t.integer "sender_name_type", default: 0, null: false | ||||
|     t.index ["account_id"], name: "index_inboxes_on_account_id" | ||||
|     t.index ["channel_id", "channel_type"], name: "index_inboxes_on_channel_id_and_channel_type" | ||||
|     t.index ["portal_id"], name: "index_inboxes_on_portal_id" | ||||
|   | ||||
| @@ -44,6 +44,7 @@ RSpec.describe ConversationReplyMailer do | ||||
|                  bcc_emails: 'agent_bcc1@example.com' | ||||
|                }) | ||||
|       end | ||||
|  | ||||
|       let(:private_message) { create(:message, account: account, content: 'This is a private message', conversation: conversation) } | ||||
|       let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now } | ||||
|       let(:cc_mail) { described_class.reply_with_summary(cc_message.conversation, message.id).deliver_now } | ||||
| @@ -186,13 +187,84 @@ RSpec.describe ConversationReplyMailer do | ||||
|         expect(mail['from'].value).to eq "#{conversation.assignee.available_name} from #{smtp_email_channel.inbox.name} <#{smtp_email_channel.email}>" | ||||
|       end | ||||
|  | ||||
|       it 'renders inbox name as sender and assignee not present' do | ||||
|       it 'renders inbox name as sender and assignee or business_name not present' do | ||||
|         message.update(sender_id: nil) | ||||
|         conversation.update(assignee_id: nil) | ||||
|  | ||||
|         mail = described_class.email_reply(message) | ||||
|         expect(mail['from'].value).to eq "Notifications from #{smtp_email_channel.inbox.name} <#{smtp_email_channel.email}>" | ||||
|       end | ||||
|  | ||||
|       context 'when friendly name enabled' do | ||||
|         before do | ||||
|           conversation.inbox.update(sender_name_type: 0) | ||||
|           conversation.inbox.update(business_name: 'Business Name') | ||||
|         end | ||||
|  | ||||
|         it 'renders sender name as sender and assignee and business_name not present' do | ||||
|           message.update(sender_id: nil) | ||||
|           conversation.update(assignee_id: nil) | ||||
|           conversation.inbox.update(business_name: nil) | ||||
|  | ||||
|           mail = described_class.email_reply(message) | ||||
|  | ||||
|           expect(mail['from'].value).to eq "Notifications from #{conversation.inbox.name} <#{smtp_email_channel.email}>" | ||||
|         end | ||||
|  | ||||
|         it 'renders sender name as sender and assignee nil and business_name present' do | ||||
|           message.update(sender_id: nil) | ||||
|           conversation.update(assignee_id: nil) | ||||
|  | ||||
|           mail = described_class.email_reply(message) | ||||
|  | ||||
|           expect(mail['from'].value).to eq( | ||||
|             "Notifications from #{conversation.inbox.business_name} <#{smtp_email_channel.email}>" | ||||
|           ) | ||||
|         end | ||||
|  | ||||
|         it 'renders sender name as sender nil and assignee and business_name present' do | ||||
|           message.update(sender_id: nil) | ||||
|           conversation.update(assignee_id: agent.id) | ||||
|  | ||||
|           mail = described_class.email_reply(message) | ||||
|           expect(mail['from'].value).to eq "#{agent.available_name} from #{conversation.inbox.business_name} <#{smtp_email_channel.email}>" | ||||
|         end | ||||
|  | ||||
|         it 'renders sender name as sender and assignee and business_name present' do | ||||
|           agent_2 = create(:user, email: 'agent2@example.com', account: account) | ||||
|           message.update(sender_id: agent_2.id) | ||||
|           conversation.update(assignee_id: agent.id) | ||||
|  | ||||
|           mail = described_class.email_reply(message) | ||||
|           expect(mail['from'].value).to eq "#{agent_2.available_name} from #{conversation.inbox.business_name} <#{smtp_email_channel.email}>" | ||||
|         end | ||||
|       end | ||||
|  | ||||
|       context 'when friendly name disabled' do | ||||
|         before do | ||||
|           conversation.inbox.update(sender_name_type: 1) | ||||
|           conversation.inbox.update(business_name: 'Business Name') | ||||
|         end | ||||
|  | ||||
|         it 'renders sender name as business_name not present' do | ||||
|           message.update(sender_id: nil) | ||||
|           conversation.update(assignee_id: nil) | ||||
|           conversation.inbox.update(business_name: nil) | ||||
|  | ||||
|           mail = described_class.email_reply(message) | ||||
|  | ||||
|           expect(mail['from'].value).to eq "#{conversation.inbox.name} <#{smtp_email_channel.email}>" | ||||
|         end | ||||
|  | ||||
|         it 'renders sender name as business_name present' do | ||||
|           message.update(sender_id: nil) | ||||
|           conversation.update(assignee_id: nil) | ||||
|  | ||||
|           mail = described_class.email_reply(message) | ||||
|  | ||||
|           expect(mail['from'].value).to eq "#{conversation.inbox.business_name} <#{smtp_email_channel.email}>" | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when smtp enabled for microsoft email channel' do | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tejaswini Chile
					Tejaswini Chile