@capillarytech/creatives-library 9.0.54-alpha.2 → 9.0.54-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.54-alpha.2",
4
+ "version": "9.0.54-alpha.4",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -710,6 +710,17 @@ export const createCommDefinition = (payload) => {
710
710
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
711
711
  };
712
712
 
713
+ // Opens a new DRAFT version with the given content on an EXISTING
714
+ // CommDefinition (same id/referenceId) — used by CommunicationFlow's edit-mode
715
+ // save (re-editing a rejected alert) instead of createCommDefinition, so the
716
+ // alert keeps its identity rather than becoming a brand-new CommDefinition.
717
+ // Route verified against a real Postman sample against Veyron's own
718
+ // /commdefinition/{id}/edit endpoint.
719
+ export const editCommDefinition = (commDefinitionId, payload) => {
720
+ const url = `${API_ENDPOINT}/comm-definitions/${commDefinitionId}/edit`;
721
+ return request(url, getAPICallObject('POST', payload, false, false, false, true));
722
+ };
723
+
713
724
  export const getCentralCommsMetaIds = (metaIds, metaType = TRANSACTION) => {
714
725
  const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}?metaIds=${metaIds}`;
715
726
  return request(url, getAPICallObject('GET', null, false, false, false, true));
@@ -21,7 +21,7 @@ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
21
21
  // import injectSaga from '../../utils/injectSaga'; // cap-coupons flows disabled
22
22
  // import injectReducer from '../../utils/injectReducer';
23
23
  import { makeSelectAuthenticated } from '../Cap/selectors';
24
- import { createCommDefinition } from '../../services/api';
24
+ import { createCommDefinition, editCommDefinition } from '../../services/api';
25
25
  import DynamicControlsStep from './steps/DynamicControlsStep';
26
26
  import MessageTypeStep from './steps/MessageTypeStep';
27
27
  import CommunicationStrategyStep from './steps/CommunicationStrategyStep';
@@ -215,14 +215,29 @@ const CommunicationFlow = ({
215
215
  singleChannelStrategy: {
216
216
  variant: {
217
217
  channel,
218
- ...(contentKey && { [contentKey]: { channel, ...contentPayload } }),
219
- ...(deliveryKey && { [deliveryKey]: { channelSettings: { channel, ...channelSettings } } }),
218
+ // `channel` spreads LAST in both objects below — contentItem.templateData
219
+ // (e.g. mobile push) carries the UI's own internal channel identifier
220
+ // (MOBILEPUSH), which must never win over CCS's normalized enum value
221
+ // (PUSH) once spread order puts it after.
222
+ ...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
223
+ ...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
220
224
  },
221
225
  },
222
226
  };
223
227
 
228
+ // Editing an existing (e.g. rejected) alert: config.context.existingCommDefinitionId
229
+ // opens a new DRAFT version on that SAME CommDefinition instead of creating a
230
+ // brand-new one, so the alert keeps its id/referenceId across the edit.
231
+ const { existingCommDefinitionId } = config?.context || {};
232
+
224
233
  try {
225
- const res = await createCommDefinition(payload);
234
+ const res = existingCommDefinitionId
235
+ ? await editCommDefinition(existingCommDefinitionId, {
236
+ strategyType: payload.strategyType,
237
+ settings: payload.settings,
238
+ singleChannelStrategy: payload.singleChannelStrategy,
239
+ })
240
+ : await createCommDefinition(payload);
226
241
  if (isDuplicateReferenceIdError(res)) {
227
242
  // Duplicate referenceId in this org — block the save so the user can
228
243
  // change it, rather than silently proceeding without a CCS comm.
@@ -230,7 +245,16 @@ const CommunicationFlow = ({
230
245
  return;
231
246
  }
232
247
  const data = res?.response?.data;
233
- if (data?.id) {
248
+ if (existingCommDefinitionId) {
249
+ if (data) {
250
+ ccsCommDefinition = {
251
+ id: existingCommDefinitionId,
252
+ referenceId: referenceId || data.referenceId,
253
+ version: data.version?.version ?? data.version ?? 1,
254
+ status: data.status,
255
+ };
256
+ }
257
+ } else if (data?.id) {
234
258
  ccsCommDefinition = {
235
259
  id: data.id,
236
260
  referenceId: data.referenceId,
@@ -42,7 +42,7 @@ const resolveValue = (val) => {
42
42
 
43
43
  const SENDER_ID_RESOLVERS = {
44
44
  [SMS]: (setting) => resolveValue(setting?.gsmSenderId),
45
- [EMAIL]: (setting) => resolveValue(setting?.senderEmail),
45
+ [EMAIL]: (setting) => resolveValue(setting?.senderId),
46
46
  [VIBER]: (setting) => resolveValue(setting?.sender) || resolveValue(setting?.gsmSenderId),
47
47
  [WHATSAPP]: (setting) => resolveValue(setting?.senderMobNum),
48
48
  [RCS]: (setting) => resolveValue(setting?.senderMobNum) || resolveValue(setting?.rcsSender),
@@ -2,6 +2,7 @@ import React from 'react';
2
2
 
3
3
  jest.mock('../../../services/api', () => ({
4
4
  createCommDefinition: jest.fn(),
5
+ editCommDefinition: jest.fn(),
5
6
  }));
6
7
 
7
8
  jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
@@ -37,7 +38,7 @@ import { IntlProvider } from 'react-intl';
37
38
  import history from '../../../utils/history';
38
39
  import { initialReducer } from '../../../initialReducer';
39
40
  import CommunicationFlow from '../CommunicationFlow';
40
- import { createCommDefinition } from '../../../services/api';
41
+ import { createCommDefinition, editCommDefinition } from '../../../services/api';
41
42
  import { getEnabledSteps } from '../utils/getEnabledSteps';
42
43
  import {
43
44
  CHANNELS,
@@ -407,7 +408,7 @@ describe('handleSave — CCS flow', () => {
407
408
  initialData: {
408
409
  contentItems: [{
409
410
  channel: 'EMAIL',
410
- templateData: { emailSubject: 'Order Confirmed', messageBody: '<html>Hi</html>' },
411
+ templateData: { emailSubject: 'Order Confirmed', emailBody: '<html>Hi</html>' },
411
412
  }],
412
413
  },
413
414
  onSave,
@@ -478,13 +479,21 @@ describe('handleSave — CCS flow', () => {
478
479
  );
479
480
  });
480
481
 
481
- it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*)', async () => {
482
+ it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*), even when templateData/channelSetting carry their own stray channel field', async () => {
482
483
  const onSave = jest.fn();
483
484
  renderWithFlow({
484
485
  features: {},
485
486
  config: ccsConfig,
486
487
  initialData: {
487
- contentItems: [{ channel: 'MOBILEPUSH', templateData: { messageSubject: 'Hi' } }],
488
+ // `channel: 'MOBILEPUSH'` inline on templateData reproduces the real
489
+ // production bug: contentItem.templateData carries the UI's own
490
+ // internal channel identifier, which must not win over the
491
+ // normalized 'PUSH' enum once spread into mpushMessageContent.
492
+ contentItems: [{ channel: 'MOBILEPUSH', templateData: { channel: 'MOBILEPUSH', messageSubject: 'Hi' } }],
493
+ // Likewise, deliverySetting.channelSetting.PUSH carrying its own
494
+ // stray `channel` key must not win over the normalized value once
495
+ // spread into mpushDeliverySettings.channelSettings.
496
+ deliverySetting: { channelSetting: { PUSH: { channel: 'MOBILEPUSH', domainId: 2002 } } },
488
497
  },
489
498
  onSave,
490
499
  });
@@ -498,6 +507,9 @@ describe('handleSave — CCS flow', () => {
498
507
  variant: expect.objectContaining({
499
508
  channel: 'PUSH',
500
509
  mpushMessageContent: { channel: 'PUSH', messageSubject: 'Hi' },
510
+ mpushDeliverySettings: {
511
+ channelSettings: { channel: 'PUSH', domainId: 2002 },
512
+ },
501
513
  }),
502
514
  }),
503
515
  }),
@@ -727,6 +739,86 @@ describe('handleSave — CCS flow', () => {
727
739
  gsmSenderId: 'BRAND1',
728
740
  });
729
741
  });
742
+
743
+ it('does not call editCommDefinition when config.context.existingCommDefinitionId is absent', async () => {
744
+ const onSave = jest.fn();
745
+ renderWithFlow({
746
+ features: {},
747
+ config: ccsConfig,
748
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
749
+ onSave,
750
+ });
751
+
752
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
753
+
754
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
755
+ expect(editCommDefinition).not.toHaveBeenCalled();
756
+ });
757
+
758
+ it('calls editCommDefinition (not createCommDefinition) and merges the existing id into ccsCommDefinition when config.context.existingCommDefinitionId is set', async () => {
759
+ editCommDefinition.mockResolvedValue({
760
+ response: { data: { version: { version: 2 }, status: 'DRAFT' } },
761
+ });
762
+ const onSave = jest.fn();
763
+ renderWithFlow({
764
+ features: {},
765
+ config: {
766
+ ...ccsConfig,
767
+ context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', existingCommDefinitionId: 'cd_existing_1' },
768
+ },
769
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
770
+ onSave,
771
+ });
772
+
773
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
774
+
775
+ await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
776
+ expect(editCommDefinition).toHaveBeenCalledWith(
777
+ 'cd_existing_1',
778
+ expect.objectContaining({
779
+ strategyType: 'SINGLE',
780
+ settings: expect.anything(),
781
+ singleChannelStrategy: expect.anything(),
782
+ }),
783
+ );
784
+ expect(createCommDefinition).not.toHaveBeenCalled();
785
+
786
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
787
+ expect(onSave).toHaveBeenCalledWith(
788
+ expect.objectContaining({
789
+ ccsCommDefinition: {
790
+ id: 'cd_existing_1',
791
+ referenceId: 'ORDER_PLACED',
792
+ version: 2,
793
+ status: 'DRAFT',
794
+ },
795
+ }),
796
+ );
797
+ });
798
+
799
+ it('blocks save and shows an error when editCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
800
+ editCommDefinition.mockResolvedValue({
801
+ success: false,
802
+ status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
803
+ message: 'REFERENCE_ID_EXISTS',
804
+ });
805
+ const onSave = jest.fn();
806
+ renderWithFlow({
807
+ features: {},
808
+ config: {
809
+ ...ccsConfig,
810
+ context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', existingCommDefinitionId: 'cd_existing_1' },
811
+ },
812
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
813
+ onSave,
814
+ });
815
+
816
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
817
+
818
+ await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
819
+ expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
820
+ expect(onSave).not.toHaveBeenCalled();
821
+ });
730
822
  });
731
823
 
732
824
  describe('renderSteps — null returns and edge cases', () => {
@@ -247,11 +247,11 @@ describe('CommunicationFlowCard', () => {
247
247
  expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
248
248
  });
249
249
 
250
- it('shows senderEmail for EMAIL with Sender ID label', () => {
250
+ it('shows senderId for EMAIL with Sender ID label', () => {
251
251
  renderCard({
252
252
  initialData: makeSavedData({
253
253
  contentItems: [{ channel: 'EMAIL', templateData: {} }],
254
- deliverySetting: { channelSetting: { EMAIL: { senderEmail: 'test@example.com' } } },
254
+ deliverySetting: { channelSetting: { EMAIL: { senderId: 'test@example.com' } } },
255
255
  }),
256
256
  });
257
257
  expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
@@ -68,14 +68,15 @@ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
68
68
  // editor (messageMeta-oriented field names) — for channels whose legacy field
69
69
  // names don't match CCS's own content schema, transform before sending rather
70
70
  // than dumping templateData as-is. EMAIL: legacy stores the subject as
71
- // `emailSubject` (see getContentBody.js), CCS expects `messageSubject`;
72
- // `messageBody` already matches CCS's name by coincidence. Channels absent
73
- // here are passed through unchanged (their legacy/CCS field names already
74
- // align, e.g. SMS's `message`/`messageBody`).
71
+ // `emailSubject` and the HTML body as `emailBody` (see
72
+ // extractContentForPreview.js and CreativesContainer/index.js, where the Bee
73
+ // editor's output is written back into templateData.emailBody also
74
+ // mirrored by the existing legacy emailMessageContent builder in
75
+ // utils/transformerUtils.js). CCS expects `messageSubject`/`messageBody`.
75
76
  export const CCS_CONTENT_TRANSFORMS = {
76
77
  EMAIL: (templateData = {}) => ({
77
78
  messageSubject: templateData.emailSubject,
78
- messageBody: templateData.messageBody,
79
+ messageBody: templateData.emailBody || templateData.emailHtml,
79
80
  }),
80
81
  // WEBPUSH: the legacy editor nests the real data under
81
82
  // messageContent.content (see CommunicationFlowCard.js's own comment on
@@ -29,6 +29,7 @@ import {
29
29
  getEmailDefaultsForDomainId,
30
30
  getSmsDefaultsForDomainId,
31
31
  getRcsAccountName,
32
+ getEmailDomainGatewayMapIdByDomainId,
32
33
  } from './deliverySettingsConfig';
33
34
  import messages from '../../messages';
34
35
  import './SenderDetails.scss';
@@ -151,6 +152,11 @@ const SenderDetails = ({
151
152
  saveValues.whatsappDomainId = matchedDomain?.domainProperties?.id ?? matchedDomain?.domainId ?? matchedDomain?.id;
152
153
  }
153
154
  }
155
+ // Include EMAIL domainGatewayMapId derived from the selected domain for persistence -
156
+ // CCS needs it alongside domainId, but it's not a user-facing field.
157
+ if (entity && fieldValues.emailDomain != null && fieldValues.emailDomain !== '') {
158
+ saveValues.emailDomainGatewayMapId = getEmailDomainGatewayMapIdByDomainId(entity, fieldValues.emailDomain);
159
+ }
154
160
  onSave(saveValues);
155
161
  }
156
162
  onClose();
@@ -83,6 +83,7 @@ const ENTITIES = {
83
83
  emailTwoDomains: {
84
84
  EMAIL: [
85
85
  {
86
+ id: 'gwmap-a',
86
87
  domainProperties: {
87
88
  id: 'em-a',
88
89
  domainName: 'Mail Alpha',
@@ -101,6 +102,7 @@ const ENTITIES = {
101
102
  },
102
103
  },
103
104
  {
105
+ id: 'gwmap-b',
104
106
  domainProperties: {
105
107
  id: 'em-b',
106
108
  domainName: 'Mail Beta',
@@ -322,6 +324,29 @@ describe('SenderDetails', () => {
322
324
  });
323
325
  });
324
326
 
327
+ it('persists emailDomainGatewayMapId derived from the selected email domain on save', async () => {
328
+ const { onSave } = renderSenderDetails({
329
+ channels: ['EMAIL'],
330
+ preloadedDomainProperties: ENTITIES.emailTwoDomains,
331
+ });
332
+ const root = document.querySelector('.sender-details');
333
+ await waitFor(() => expect(within(root).getByText('Mail Alpha')).toBeInTheDocument());
334
+
335
+ await openSelectAndChoose(root, 0, 'Mail Beta');
336
+ await waitFor(() => expect(within(root).getByText('beta@other.com')).toBeInTheDocument());
337
+
338
+ const saveBtn = within(root).getByRole('button', { name: /save changes/i });
339
+ await waitFor(() => expect(saveBtn).not.toBeDisabled());
340
+ fireEvent.click(saveBtn);
341
+
342
+ expect(onSave).toHaveBeenCalledWith(
343
+ expect.objectContaining({
344
+ emailDomain: 'em-b',
345
+ emailDomainGatewayMapId: 'gwmap-b',
346
+ }),
347
+ );
348
+ });
349
+
325
350
  it('resets email domain row back to the first gateway', async () => {
326
351
  renderSenderDetails({
327
352
  channels: ['EMAIL'],
@@ -438,6 +438,7 @@ describe('deliverySettingsConfig — user-facing flows', () => {
438
438
  smsDomain: 'd1',
439
439
  smsSenderId: 'g1',
440
440
  emailDomain: 'ed',
441
+ emailDomainGatewayMapId: 'gwmap-1',
441
442
  emailSenderId: 'mail@x.com',
442
443
  emailSenderName: 'N',
443
444
  emailReplyToId: 'r@x.com',
@@ -455,9 +456,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
455
456
  SMS: { domainId: 'd1', gsmSenderId: 'g1' },
456
457
  EMAIL: {
457
458
  domainId: 'ed',
458
- senderEmail: 'mail@x.com',
459
+ domainGatewayMapId: 'gwmap-1',
460
+ senderId: 'mail@x.com',
459
461
  senderLabel: 'N',
460
- senderReplyTo: 'r@x.com',
462
+ replyToId: 'r@x.com',
461
463
  },
462
464
  WHATSAPP: { domainId: 'wd', senderMobNum: '+w' },
463
465
  RCS: { senderMobNum: '+r', rcsSender: '+r' },
@@ -472,9 +474,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
472
474
  SMS: { domainId: 'd', gsmSenderId: 'g' },
473
475
  EMAIL: {
474
476
  domainId: 'ed',
475
- senderEmail: 'e@e.com',
477
+ domainGatewayMapId: 'gwmap-2',
478
+ senderId: 'e@e.com',
476
479
  senderLabel: 'L',
477
- senderReplyTo: 'r@e.com',
480
+ replyToId: 'r@e.com',
478
481
  },
479
482
  WHATSAPP: { senderMobNum: '+w', domainId: 'wid' },
480
483
  RCS: { rcsSender: '+r' },
@@ -486,6 +489,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
486
489
  smsDomain: 'd',
487
490
  smsSenderId: 'g',
488
491
  emailDomain: 'ed',
492
+ emailDomainGatewayMapId: 'gwmap-2',
493
+ emailSenderId: 'e@e.com',
494
+ emailSenderName: 'L',
495
+ emailReplyToId: 'r@e.com',
489
496
  whatsappDomainId: 'wid',
490
497
  viberSenderId: 'vx',
491
498
  });
@@ -181,6 +181,14 @@ const getEmailDomainValue = (entity) => {
181
181
  return emailData.domainProperties?.id ?? emailData.domainId ?? emailData.id ?? '';
182
182
  };
183
183
 
184
+ // CCS's domainGatewayMapId is the domainProperties API's own outer `id`
185
+ // (distinct from `domainProperties.id`, which is the numeric domainId) - the
186
+ // domain-gateway-mapping id for the selected domain entry.
187
+ const getEmailDomainGatewayMapIdByDomainId = (entity, domainId) => {
188
+ const domainEntry = getEmailDomainById(entity, domainId);
189
+ return domainEntry?.id ?? '';
190
+ };
191
+
184
192
  /** Domain name for summary display (parseEntityForDisplay) */
185
193
  const getEmailDomainNameForDisplay = (entity) => {
186
194
  const emailData = getEmailData(entity);
@@ -262,6 +270,8 @@ const getEmailDefaultsForDomain = (domainData) => {
262
270
  * context.fieldValues has current form state (e.g. selected emailDomain)
263
271
  * Aligned with adiona-ui/cap-campaigns-v2: default = contactInfo item with default:true, or first
264
272
  */
273
+ export { getEmailDomainGatewayMapIdByDomainId };
274
+
265
275
  export const getDefaultValueForField = (fieldKey, entity, context = {}) => {
266
276
  if (!entity) return '';
267
277
  const fieldValues = context?.fieldValues || {};
@@ -574,7 +584,11 @@ export const parseEntityForDisplay = (entity, context = {}) => {
574
584
  senderDetails: entity,
575
585
  smsSenderId: getSmsSenderIdValue(entity),
576
586
  smsDomain: getSmsDomainValue(entity),
587
+ // emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues
588
+ // needs the numeric domainId/domainGatewayMapId, sourced separately below.
577
589
  emailDomain: getEmailDomainNameForDisplay(entity),
590
+ emailDomainId: getEmailDomainValue(entity),
591
+ emailDomainGatewayMapId: getEmailDomainGatewayMapIdByDomainId(entity, getEmailDomainValue(entity)),
578
592
  emailSenderId: getEmailSenderIdValue(entity),
579
593
  viberSenderId: getSenderIdFromContactInfo(entity, 'VIBER'),
580
594
  zaloSenderId: getSenderIdFromContactInfo(entity, 'ZALO'),
@@ -600,10 +614,14 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
600
614
  }
601
615
  if (fieldValues.emailDomain != null || fieldValues.emailSenderId != null || fieldValues.emailSenderName != null || fieldValues.emailReplyToId != null) {
602
616
  channelSetting.EMAIL = {
603
- domainId: fieldValues.emailDomain,
604
- senderEmail: fieldValues.emailSenderId,
617
+ // emailDomainId (numeric, from the auto-save-defaults path via parseEntityForDisplay)
618
+ // takes priority over emailDomain, which is numeric when sourced from SenderDetails'
619
+ // own field state but a display NAME string when sourced from parseEntityForDisplay.
620
+ domainId: fieldValues.emailDomainId ?? fieldValues.emailDomain,
621
+ domainGatewayMapId: fieldValues.emailDomainGatewayMapId,
622
+ senderId: fieldValues.emailSenderId,
605
623
  senderLabel: fieldValues.emailSenderName,
606
- senderReplyTo: fieldValues.emailReplyToId,
624
+ replyToId: fieldValues.emailReplyToId,
607
625
  };
608
626
  }
609
627
  if (fieldValues.whatsappSenderNumber != null || fieldValues.whatsappAccount != null) {
@@ -673,9 +691,10 @@ export const parseChannelSettingForDisplay = (channelSetting = {}) => {
673
691
  }
674
692
  if (channelSetting.EMAIL) {
675
693
  out.emailDomain = channelSetting.EMAIL.domainId;
676
- out.emailSenderId = channelSetting.EMAIL.senderEmail;
694
+ out.emailDomainGatewayMapId = channelSetting.EMAIL.domainGatewayMapId;
695
+ out.emailSenderId = channelSetting.EMAIL.senderId;
677
696
  out.emailSenderName = channelSetting.EMAIL.senderLabel;
678
- out.emailReplyToId = channelSetting.EMAIL.senderReplyTo;
697
+ out.emailReplyToId = channelSetting.EMAIL.replyToId;
679
698
  }
680
699
  if (channelSetting.WHATSAPP) {
681
700
  out.whatsappSenderNumber = channelSetting.WHATSAPP.senderMobNum;