From a0c04ed1905a6b59a13eac27f351916bce5b2adf Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 31 Jan 2018 14:13:23 +0100 Subject: [PATCH] Fixed issue# 1790 - On reply of an email, the recipient will not be set if sender header contains invalid email addresses. --- .../javascripts/app/lib/app_post/utils.coffee | 45 ++- .../app/lib/base/email-addresses.js | 317 ++++++++++++------ public/assets/tests/html_utils.js | 226 +++++++++---- 3 files changed, 419 insertions(+), 169 deletions(-) diff --git a/app/assets/javascripts/app/lib/app_post/utils.coffee b/app/assets/javascripts/app/lib/app_post/utils.coffee index 74142bd5f..0bc5a7f97 100644 --- a/app/assets/javascripts/app/lib/app_post/utils.coffee +++ b/app/assets/javascripts/app/lib/app_post/utils.coffee @@ -890,6 +890,29 @@ class App.Utils text = text.replace(/http(s|):\/\/[-A-Za-z0-9+&@#\/%?=~_\|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]/img, placeholder) text.length + @parseAddressListLocal: (line) -> + recipients = emailAddresses.parseAddressList(line) + result = [] + if !_.isEmpty(recipients) + for recipient in recipients + if recipient && recipient.address + result.push recipient.address + return result + + # workaround for email-addresses.js issue with this kind of + # mail headers "From: invalid sender, realname " + # email-addresses.js is returning null because it can't parse the + # whole header + if _.isEmpty(recipients) && line.match('@') + recipients = line.split(',') + re = /(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/ + for recipient in recipients + if recipient && recipient.match('@') + localResult = recipient.match(re) + if localResult && localResult[0] + result.push localResult[0] + result + @getRecipientArticle: (ticket, article, article_created_by, type, email_addresses = [], all) -> # empty form @@ -938,16 +961,18 @@ class App.Utils # check if article sender is local senderIsLocal = false if !_.isEmpty(article.from) - senders = emailAddresses.parseAddressList(article.from) - if senders && senders[0] && senders[0].address - senderIsLocal = isLocalAddress(senders[0].address) + senders = App.Utils.parseAddressListLocal(article.from) + if senders + for sender in senders + if sender && sender.address && sender.address.match('@') + senderIsLocal = isLocalAddress(sender.address) # check if article recipient is local recipientIsLocal = false if !_.isEmpty(article.to) - recipients = emailAddresses.parseAddressList(article.to) - if recipients && recipients[0] && recipients[0].address - recipientIsLocal = isLocalAddress(recipients[0].address) + recipients = App.Utils.parseAddressListLocal(article.to) + if recipients && recipients[0] + recipientIsLocal = isLocalAddress(recipients[0]) # sender is local if senderIsLocal @@ -971,14 +996,14 @@ class App.Utils # filter for uniq recipients recipientAddresses = {} - addAddresses = (addressLine, line) -> lineNew = '' - recipients = emailAddresses.parseAddressList(addressLine) + recipients = App.Utils.parseAddressListLocal(addressLine) + if !_.isEmpty(recipients) for recipient in recipients - if !_.isEmpty(recipient.address) - localRecipientAddress = recipient.address.toString().toLowerCase() + if !_.isEmpty(recipient) + localRecipientAddress = recipient.toString().toLowerCase() # check if address is not local if !isLocalAddress(localRecipientAddress) diff --git a/app/assets/javascripts/app/lib/base/email-addresses.js b/app/assets/javascripts/app/lib/base/email-addresses.js index 4f36f700b..ce7b30d85 100644 --- a/app/assets/javascripts/app/lib/base/email-addresses.js +++ b/app/assets/javascripts/app/lib/base/email-addresses.js @@ -1,6 +1,6 @@ // email-addresses.js - RFC 5322 email address parser -// v 2.0.1 +// v 3.0.1 // // http://tools.ietf.org/html/rfc5322 // @@ -186,27 +186,7 @@ function parse5322(opts) { // "First Last" -> "First Last" // "First Last" -> "First Last" function collapseWhitespace(s) { - function isWhitespace(c) { - return c === ' ' || - c === '\t' || - c === '\r' || - c === '\n'; - } - var i, str; - str = ""; - for (i = 0; i < s.length; i += 1) { - if (!isWhitespace(s[i]) || !isWhitespace(s[i + 1])) { - str += s[i]; - } - } - - if (isWhitespace(str[0])) { - str = str.substring(1); - } - if (isWhitespace(str[str.length - 1])) { - str = str.substring(0, str.length - 1); - } - return str; + return s.replace(/([ \t]|\r\n)+/g, ' ').replace(/^\s*/, '').replace(/\s*$/, ''); } // UTF-8 pseudo-production (RFC 6532) @@ -597,10 +577,14 @@ function parse5322(opts) { return wrap('domain', function domainCheckTLD() { var result = or(obsDomain, dotAtom, domainLiteral)(); if (opts.rejectTLD) { - if (result.semantic.indexOf('.') < 0) { + if (result && result.semantic && result.semantic.indexOf('.') < 0) { return null; } } + // strip all whitespace from domains + if (result) { + result.semantic = result.semantic.replace(/\s+/g, ''); + } return result; }()); } @@ -612,6 +596,36 @@ function parse5322(opts) { )()); } + // 3.6.2 Originator Fields + // Below we only parse the field body, not the name of the field + // like "From:", "Sender:", or "Reply-To:". Other libraries that + // parse email headers can parse those and defer to these productions + // for the "RFC 5322" part. + + // RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields + // from = "From:" (mailbox-list / address-list) CRLF + function fromSpec() { + return wrap('from', or( + mailboxList, + addressList + )()); + } + + // RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields + // sender = "Sender:" (mailbox / address) CRLF + function senderSpec() { + return wrap('sender', or( + mailbox, + address + )()); + } + + // RFC 6854 2.1. Replacement of RFC 5322, Section 3.6.2. Originator Fields + // reply-to = "Reply-To:" address-list CRLF + function replyToSpec() { + return wrap('reply-to', addressList()); + } + // 4.1. Miscellaneous Obsolete Tokens // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control @@ -766,92 +780,186 @@ function parse5322(opts) { // ast analysis function findNode(name, root) { - var i, queue, node; + var i, stack, node; if (root === null || root === undefined) { return null; } - queue = [root]; - while (queue.length > 0) { - node = queue.shift(); + stack = [root]; + while (stack.length > 0) { + node = stack.pop(); if (node.name === name) { return node; } - for (i = 0; i < node.children.length; i += 1) { - queue.push(node.children[i]); + for (i = node.children.length - 1; i >= 0; i -= 1) { + stack.push(node.children[i]); } } return null; } function findAllNodes(name, root) { - var i, queue, node, result; + var i, stack, node, result; if (root === null || root === undefined) { return null; } - queue = [root]; + stack = [root]; result = []; - while (queue.length > 0) { - node = queue.shift(); + while (stack.length > 0) { + node = stack.pop(); if (node.name === name) { result.push(node); } - for (i = 0; i < node.children.length; i += 1) { - queue.push(node.children[i]); + for (i = node.children.length - 1; i >= 0; i -= 1) { + stack.push(node.children[i]); + } + } + return result; + } + + function findAllNodesNoChildren(names, root) { + var i, stack, node, result, namesLookup; + if (root === null || root === undefined) { return null; } + stack = [root]; + result = []; + namesLookup = {}; + for (i = 0; i < names.length; i += 1) { + namesLookup[names[i]] = true; + } + + while (stack.length > 0) { + node = stack.pop(); + if (node.name in namesLookup) { + result.push(node); + // don't look at children (hence findAllNodesNoChildren) + } else { + for (i = node.children.length - 1; i >= 0; i -= 1) { + stack.push(node.children[i]); + } } } return result; } function giveResult(ast) { - function grabSemantic(n) { - return n !== null ? n.semantic : null; - } - var i, ret, addresses, addr, name, aspec, local, domain; + var addresses, groupsAndMailboxes, i, groupOrMailbox, result; if (ast === null) { return null; } - ret = { ast: ast }; - addresses = findAllNodes('address', ast); - ret.addresses = []; - for (i = 0; i < addresses.length; i += 1) { - addr = addresses[i]; - name = findNode('display-name', addr); - aspec = findNode('addr-spec', addr); - local = findNode('local-part', aspec); - domain = findNode('domain', aspec); - ret.addresses.push({ - node: addr, - parts: { - name: name, - address: aspec, - local: local, - domain: domain - }, - name: grabSemantic(name), - address: grabSemantic(aspec), - local: grabSemantic(local), - domain: grabSemantic(domain) - }); - } + addresses = []; - if (opts.simple) { - ret = ret.addresses; - for (i = 0; i < ret.length; i += 1) { - delete ret[i].node; + // An address is a 'group' (i.e. a list of mailboxes) or a 'mailbox'. + groupsAndMailboxes = findAllNodesNoChildren(['group', 'mailbox'], ast); + for (i = 0; i < groupsAndMailboxes.length; i += 1) { + groupOrMailbox = groupsAndMailboxes[i]; + if (groupOrMailbox.name === 'group') { + addresses.push(giveResultGroup(groupOrMailbox)); + } else if (groupOrMailbox.name === 'mailbox') { + addresses.push(giveResultMailbox(groupOrMailbox)); } } - return ret; + + result = { + ast: ast, + addresses: addresses, + }; + if (opts.simple) { + result = simplifyResult(result); + } + if (opts.oneResult) { + return oneResult(result); + } + if (opts.simple) { + return result && result.addresses; + } else { + return result; + } + } + + function giveResultGroup(group) { + var i; + var groupName = findNode('display-name', group); + var groupResultMailboxes = []; + var mailboxes = findAllNodesNoChildren(['mailbox'], group); + for (i = 0; i < mailboxes.length; i += 1) { + groupResultMailboxes.push(giveResultMailbox(mailboxes[i])); + } + return { + node: group, + parts: { + name: groupName, + }, + type: group.name, // 'group' + name: grabSemantic(groupName), + addresses: groupResultMailboxes, + }; + } + + function giveResultMailbox(mailbox) { + var name = findNode('display-name', mailbox); + var aspec = findNode('addr-spec', mailbox); + var comments = findAllNodes('cfws', mailbox); + + var local = findNode('local-part', aspec); + var domain = findNode('domain', aspec); + return { + node: mailbox, + parts: { + name: name, + address: aspec, + local: local, + domain: domain, + comments: comments + }, + type: mailbox.name, // 'mailbox' + name: grabSemantic(name), + address: grabSemantic(aspec), + local: grabSemantic(local), + domain: grabSemantic(domain), + groupName: grabSemantic(mailbox.groupName), + }; + } + + function grabSemantic(n) { + return n !== null && n !== undefined ? n.semantic : null; + } + + function simplifyResult(result) { + var i; + if (result && result.addresses) { + for (i = 0; i < result.addresses.length; i += 1) { + delete result.addresses[i].node; + } + } + return result; + } + + function oneResult(result) { + if (!result) { return null; } + if (!opts.partial && result.addresses.length > 1) { return null; } + return result.addresses && result.addresses[0]; } ///////////////////////////////////////////////////// - var parseString, pos, len, parsed; + var parseString, pos, len, parsed, startProduction; opts = handleOpts(opts, {}); if (opts === null) { return null; } parseString = opts.input; + startProduction = { + 'address': address, + 'address-list': addressList, + 'angle-addr': angleAddr, + 'from': fromSpec, + 'group': group, + 'mailbox': mailbox, + 'mailbox-list': mailboxList, + 'reply-to': replyToSpec, + 'sender': senderSpec, + }[opts.startAt] || addressList; + if (!opts.strict) { initialize(); opts.strict = true; - parsed = addressList(parseString); + parsed = startProduction(parseString); if (opts.partial || !inStr()) { return giveResult(parsed); } @@ -859,46 +967,51 @@ function parse5322(opts) { } initialize(); - parsed = addressList(parseString); + parsed = startProduction(parseString); if (!opts.partial && inStr()) { return null; } return giveResult(parsed); } function parseOneAddressSimple(opts) { - var result; - - opts = handleOpts(opts, { + return parse5322(handleOpts(opts, { + oneResult: true, rfc6532: true, - simple: true - }); - if (opts === null) { return null; } - - result = parse5322(opts); - - if ((!result) || - (!opts.partial && - (opts.simple && result.length > 1) || - (!opts.simple && result.addresses.length > 1))) { - return null; - } - - return opts.simple ? - result && result[0] : - result && result.addresses && result.addresses[0]; + simple: true, + startAt: 'address-list', + })); } function parseAddressListSimple(opts) { - var result; - - opts = handleOpts(opts, { + return parse5322(handleOpts(opts, { rfc6532: true, - simple: true - }); - if (opts === null) { return null; } + simple: true, + startAt: 'address-list', + })); +} - result = parse5322(opts); +function parseFromSimple(opts) { + return parse5322(handleOpts(opts, { + rfc6532: true, + simple: true, + startAt: 'from', + })); +} - return opts.simple ? result : result.addresses; +function parseSenderSimple(opts) { + return parse5322(handleOpts(opts, { + oneResult: true, + rfc6532: true, + simple: true, + startAt: 'sender', + })); +} + +function parseReplyToSimple(opts) { + return parse5322(handleOpts(opts, { + rfc6532: true, + simple: true, + startAt: 'reply-to', + })); } function handleOpts(opts, defs) { @@ -926,24 +1039,28 @@ function handleOpts(opts, defs) { if (!defs) { return null; } defaults = { - rfc6532: false, + oneResult: false, partial: false, + rejectTLD: false, + rfc6532: false, simple: false, + startAt: 'address-list', strict: false, - rejectTLD: false }; for (o in defaults) { if (isNullUndef(opts[o])) { opts[o] = !isNullUndef(defs[o]) ? defs[o] : defaults[o]; } - opts[o] = !!opts[o]; } return opts; } parse5322.parseOneAddress = parseOneAddressSimple; parse5322.parseAddressList = parseAddressListSimple; +parse5322.parseFrom = parseFromSimple; +parse5322.parseSender = parseSenderSimple; +parse5322.parseReplyTo = parseReplyToSimple; // in Zammad context, go back to non CommonJS // if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { diff --git a/public/assets/tests/html_utils.js b/public/assets/tests/html_utils.js index 40e6f01bf..bc8316d5b 100644 --- a/public/assets/tests/html_utils.js +++ b/public/assets/tests/html_utils.js @@ -1982,34 +1982,106 @@ test('check getRecipientArticle format', function() { lastname: 'lastname', email: 'customer@example.com', } - agent = { - login: 'login', - firstname: 'firstname', - lastname: 'lastname', - email: 'agent@example.com', - } ticket = { customer: customer, } article = { message_id: 'message_id7', - created_by: agent, type: { name: 'email', }, sender: { - name: 'Agent', + name: 'Customer', }, - from: 'customer2@example.com', - to: 'customer@example.com', + from: 'some other invalid part, ' + customer.email, + to: 'some group', + created_by: { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'article_created_by@example.com', + } } result = { - to: 'customer2@example.com', + to: 'customer@example.com', cc: '', body: '', in_reply_to: 'message_id7', } verify = App.Utils.getRecipientArticle(ticket, article, article.created_by, article.type) + console.log(verify) + deepEqual(verify, result) + + customer = { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'customer@example.com', + } + ticket = { + customer: customer, + } + article = { + message_id: 'message_id7.1', + type: { + name: 'email', + }, + sender: { + name: 'Customer', + }, + from: 'some other invalid part, Some Realname ' + customer.email, + to: 'some group', + created_by: { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'article_created_by@example.com', + } + } + result = { + to: 'customer@example.com', + cc: '', + body: '', + in_reply_to: 'message_id7.1', + } + verify = App.Utils.getRecipientArticle(ticket, article, article.created_by, article.type) + console.log(verify) + deepEqual(verify, result) + + customer = { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'customer@example.com', + } + ticket = { + customer: customer, + } + article = { + message_id: 'message_id7.2', + type: { + name: 'email', + }, + sender: { + name: 'Customer', + }, + from: 'some other invalid part, Some Realname ' + customer.email + ' , abc', + to: 'some group', + created_by: { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'article_created_by@example.com', + } + } + result = { + to: 'customer@example.com', + cc: '', + body: '', + in_reply_to: 'message_id7.2', + } + verify = App.Utils.getRecipientArticle(ticket, article, article.created_by, article.type) + console.log(verify) deepEqual(verify, result) customer = { @@ -2036,11 +2108,11 @@ test('check getRecipientArticle format', function() { sender: { name: 'Agent', }, - from: 'agent@example.com', + from: 'customer2@example.com', to: 'customer@example.com', } result = { - to: 'customer@example.com', + to: 'customer2@example.com', cc: '', body: '', in_reply_to: 'message_id8', @@ -2072,9 +2144,8 @@ test('check getRecipientArticle format', function() { sender: { name: 'Agent', }, - from: 'Agent@Example.com', + from: 'agent@example.com', to: 'customer@example.com', - cc: 'zammad@example.com', } result = { to: 'customer@example.com', @@ -2110,16 +2181,16 @@ test('check getRecipientArticle format', function() { name: 'Agent', }, from: 'Agent@Example.com', - to: 'customer@example.com, agent@example.com', + to: 'customer@example.com', cc: 'zammad@example.com', } result = { - to: 'customer@example.com, agent@example.com', - cc: 'zammad@example.com', + to: 'customer@example.com', + cc: '', body: '', in_reply_to: 'message_id10', } - verify = App.Utils.getRecipientArticle(ticket, article, article.created_by, article.type, [], true) + verify = App.Utils.getRecipientArticle(ticket, article, article.created_by, article.type) deepEqual(verify, result) customer = { @@ -2147,8 +2218,8 @@ test('check getRecipientArticle format', function() { name: 'Agent', }, from: 'Agent@Example.com', - to: 'customeR@EXAMPLE.com, agent@example.com', - cc: 'zammad@example.com, customer@example.com', + to: 'customer@example.com, agent@example.com', + cc: 'zammad@example.com', } result = { to: 'customer@example.com, agent@example.com', @@ -2156,7 +2227,7 @@ test('check getRecipientArticle format', function() { body: '', in_reply_to: 'message_id11', } - verify = App.Utils.getRecipientArticle(ticket, article, agent, article.type, [], true) + verify = App.Utils.getRecipientArticle(ticket, article, article.created_by, article.type, [], true) deepEqual(verify, result) customer = { @@ -2184,24 +2255,16 @@ test('check getRecipientArticle format', function() { name: 'Agent', }, from: 'Agent@Example.com', - to: 'customeR@EXAMPLE.com, agent@example.com, zammad2@EXAMPLE.com', - cc: 'zammad@example.com, customer2@example.com', + to: 'customeR@EXAMPLE.com, agent@example.com', + cc: 'zammad@example.com, customer@example.com', } result = { to: 'customer@example.com, agent@example.com', - cc: 'customer2@example.com', + cc: 'zammad@example.com', body: '', in_reply_to: 'message_id12', } - email_addresses = [ - { - email: 'zammad@example.com', - }, - { - email: 'zammad2@example.com', - } - ] - verify = App.Utils.getRecipientArticle(ticket, article, agent, article.type, email_addresses, true) + verify = App.Utils.getRecipientArticle(ticket, article, agent, article.type, [], true) deepEqual(verify, result) customer = { @@ -2214,7 +2277,7 @@ test('check getRecipientArticle format', function() { login: 'login', firstname: 'firstname', lastname: 'lastname', - email: 'AGENT@example.com', + email: 'agent@example.com', } ticket = { customer: customer, @@ -2253,21 +2316,27 @@ test('check getRecipientArticle format', function() { login: 'login', firstname: 'firstname', lastname: 'lastname', - email: 'zammad@example.com', + email: 'customer@example.com', + } + agent = { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'AGENT@example.com', } ticket = { customer: customer, } article = { message_id: 'message_id14', - created_by: customer, + created_by: agent, type: { name: 'email', }, sender: { name: 'Agent', }, - from: 'zammad@EXAMPLE.com', + from: 'Agent@Example.com', to: 'customeR@EXAMPLE.com, agent@example.com, zammad2@EXAMPLE.com', cc: 'zammad@example.com, customer2@example.com', } @@ -2292,7 +2361,7 @@ test('check getRecipientArticle format', function() { login: 'login', firstname: 'firstname', lastname: 'lastname', - email: 'customer@example.com', + email: 'zammad@example.com', } ticket = { customer: customer, @@ -2306,13 +2375,13 @@ test('check getRecipientArticle format', function() { sender: { name: 'Agent', }, - from: 'customer@example.com', - to: 'customer1@example.com, customer2@example.com, zammad@example.com', - cc: '', + from: 'zammad@EXAMPLE.com', + to: 'customeR@EXAMPLE.com, agent@example.com, zammad2@EXAMPLE.com', + cc: 'zammad@example.com, customer2@example.com', } result = { - to: 'customer1@example.com, customer2@example.com, customer@example.com', - cc: '', + to: 'customer@example.com, agent@example.com', + cc: 'customer2@example.com', body: '', in_reply_to: 'message_id15', } @@ -2346,11 +2415,11 @@ test('check getRecipientArticle format', function() { name: 'Agent', }, from: 'customer@example.com', - to: 'customer1@example.com, customer2@example.com, zammad@example.com, customer2+2@example.com', + to: 'customer1@example.com, customer2@example.com, zammad@example.com', cc: '', } result = { - to: 'customer1@example.com, customer2@example.com, customer2+2@example.com, customer@example.com', + to: 'customer1@example.com, customer2@example.com, customer@example.com', cc: '', body: '', in_reply_to: 'message_id16', @@ -2372,30 +2441,24 @@ test('check getRecipientArticle format', function() { lastname: 'lastname', email: 'customer@example.com', } - agent = { - login: 'login', - firstname: 'firstname', - lastname: 'lastname', - email: 'zammad@example.com', - } ticket = { customer: customer, } article = { message_id: 'message_id17', - created_by: agent, + created_by: customer, type: { name: 'email', }, sender: { name: 'Agent', }, - from: 'zammad@example.com', - to: 'customer@example.com', + from: 'customer@example.com', + to: 'customer1@example.com, customer2@example.com, zammad@example.com, customer2+2@example.com', cc: '', } result = { - to: 'customer@example.com', + to: 'customer1@example.com, customer2@example.com, customer2+2@example.com, customer@example.com', cc: '', body: '', in_reply_to: 'message_id17', @@ -2435,6 +2498,51 @@ test('check getRecipientArticle format', function() { sender: { name: 'Agent', }, + from: 'zammad@example.com', + to: 'customer@example.com', + cc: '', + } + result = { + to: 'customer@example.com', + cc: '', + body: '', + in_reply_to: 'message_id18', + } + email_addresses = [ + { + email: 'zammad@example.com', + }, + { + email: 'zammad2@example.com', + } + ] + verify = App.Utils.getRecipientArticle(ticket, article, agent, article.type, email_addresses, true) + deepEqual(verify, result) + + customer = { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'customer@example.com', + } + agent = { + login: 'login', + firstname: 'firstname', + lastname: 'lastname', + email: 'zammad@example.com', + } + ticket = { + customer: customer, + } + article = { + message_id: 'message_id19', + created_by: agent, + type: { + name: 'email', + }, + sender: { + name: 'Agent', + }, from: 'Sender ', to: 'Customer ', cc: '', @@ -2443,7 +2551,7 @@ test('check getRecipientArticle format', function() { to: 'customer@example.com', cc: '', body: '', - in_reply_to: 'message_id18', + in_reply_to: 'message_id19', } email_addresses = [ { @@ -2466,7 +2574,7 @@ test('check getRecipientArticle format', function() { customer: agent, } article = { - message_id: 'message_id19', + message_id: 'message_id20', created_by: agent, type: { name: 'email', @@ -2482,7 +2590,7 @@ test('check getRecipientArticle format', function() { to: 'agent@example.com', cc: '', body: '', - in_reply_to: 'message_id19', + in_reply_to: 'message_id20', } email_addresses = [ {