diff --git a/application/Espo/Binding.php b/application/Espo/Binding.php
index 3403b46aec..ac4056428a 100644
--- a/application/Espo/Binding.php
+++ b/application/Espo/Binding.php
@@ -317,6 +317,11 @@ class Binding implements BindingProcessor
'Espo\\Core\\Mail\\Importer',
'Espo\\Core\\Mail\\Importer\\DefaultImporter'
);
+
+ $binder->bindImplementation(
+ 'Espo\\Core\\Mail\\Importer\\AutoReplyDetector',
+ 'Espo\\Core\\Mail\\Importer\\DefaultAutoReplyDetector'
+ );
}
private function bindAcl(Binder $binder): void
diff --git a/application/Espo/Core/Mail/Account/GroupAccount/Hooks/AfterFetch.php b/application/Espo/Core/Mail/Account/GroupAccount/Hooks/AfterFetch.php
index 95f2dce0d7..120d9fa17f 100644
--- a/application/Espo/Core/Mail/Account/GroupAccount/Hooks/AfterFetch.php
+++ b/application/Espo/Core/Mail/Account/GroupAccount/Hooks/AfterFetch.php
@@ -244,26 +244,22 @@ class AfterFetch implements AfterFetchInterface
$subject = $replyData->getSubject();
- if ($case) {
- $subject = '[#' . $case->get('number'). '] ' . $subject;
+ if ($case && $case->getNumber() !== null) {
+ $subject = "[#{$case->getNumber()}] $subject";
}
- /** @var Email $reply */
$reply = $this->entityManager->getRDBRepositoryByClass(Email::class)->getNew();
$reply
->addToAddress($fromAddress)
->setSubject($subject)
->setBody($replyData->getBody())
- ->setIsHtml($replyData->isHtml());
-
- if ($email->has('teamsIds')) {
- $reply->set('teamsIds', $email->get('teamsIds'));
- }
+ ->setIsHtml($replyData->isHtml())
+ ->setIsAutoReply()
+ ->setTeams($email->getTeams());
if ($email->getParentId() && $email->getParentType()) {
- $reply->set('parentId', $email->getParentId());
- $reply->set('parentType', $email->getParentType());
+ $reply->setParent($email->getParent());
}
$this->entityManager->saveEntity($reply);
diff --git a/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php b/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php
index cbe33bf395..61da506be0 100644
--- a/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php
+++ b/application/Espo/Core/Mail/Account/GroupAccount/Hooks/BeforeFetch.php
@@ -32,6 +32,7 @@ namespace Espo\Core\Mail\Account\GroupAccount\Hooks;
use Espo\Core\Mail\Account\Hook\BeforeFetch as BeforeFetchInterface;
use Espo\Core\Mail\Account\Hook\BeforeFetchResult;
use Espo\Core\Mail\Account\Account;
+use Espo\Core\Mail\Importer\AutoReplyDetector;
use Espo\Core\Mail\Message;
use Espo\Core\Mail\Account\GroupAccount\BouncedRecognizer;
use Espo\Core\Utils\Log;
@@ -50,6 +51,7 @@ class BeforeFetch implements BeforeFetchInterface
private EntityManager $entityManager,
private BouncedRecognizer $bouncedRecognizer,
private CampaignService $campaignService,
+ private AutoReplyDetector $autoReplyDetector,
) {}
public function process(Account $account, Message $message): BeforeFetchResult
@@ -125,22 +127,7 @@ class BeforeFetch implements BeforeFetchInterface
private function checkMessageIsAutoReply(Message $message): bool
{
- if ($message->getHeader('X-Autoreply')) {
- return true;
- }
-
- if ($message->getHeader('X-Autorespond')) {
- return true;
- }
-
- if (
- $message->getHeader('Auto-submitted') &&
- strtolower($message->getHeader('Auto-submitted')) !== 'no'
- ) {
- return true;
- }
-
- return false;
+ return $this->autoReplyDetector->detect($message);
}
private function checkMessageCannotBeAutoReplied(Message $message): bool
diff --git a/application/Espo/Core/Mail/Importer/AutoReplyDetector.php b/application/Espo/Core/Mail/Importer/AutoReplyDetector.php
new file mode 100644
index 0000000000..2c6a04764b
--- /dev/null
+++ b/application/Espo/Core/Mail/Importer/AutoReplyDetector.php
@@ -0,0 +1,42 @@
+.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+namespace Espo\Core\Mail\Importer;
+
+use Espo\Core\Mail\Message;
+
+/**
+ * Detects if an email is auto-response.
+ *
+ * @since 9.2.0
+ */
+interface AutoReplyDetector
+{
+ public function detect(Message $message): bool;
+}
diff --git a/application/Espo/Core/Mail/Importer/DefaultAutoReplyDetector.php b/application/Espo/Core/Mail/Importer/DefaultAutoReplyDetector.php
new file mode 100644
index 0000000000..eac8e75cfa
--- /dev/null
+++ b/application/Espo/Core/Mail/Importer/DefaultAutoReplyDetector.php
@@ -0,0 +1,55 @@
+.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+namespace Espo\Core\Mail\Importer;
+
+use Espo\Core\Mail\Message;
+
+class DefaultAutoReplyDetector implements AutoReplyDetector
+{
+ public function detect(Message $message): bool
+ {
+ if ($message->getHeader('X-Autoreply')) {
+ return true;
+ }
+
+ if ($message->getHeader('X-Autorespond')) {
+ return true;
+ }
+
+ if (
+ $message->getHeader('Auto-submitted') &&
+ strtolower($message->getHeader('Auto-submitted')) !== 'no'
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/application/Espo/Core/Mail/Importer/DefaultImporter.php b/application/Espo/Core/Mail/Importer/DefaultImporter.php
index 1f53fa1f5f..4ec21632ed 100644
--- a/application/Espo/Core/Mail/Importer/DefaultImporter.php
+++ b/application/Espo/Core/Mail/Importer/DefaultImporter.php
@@ -84,7 +84,8 @@ class DefaultImporter implements Importer
private LinkMultipleSaver $linkMultipleSaver,
private DuplicateFinder $duplicateFinder,
private JobSchedulerFactory $jobSchedulerFactory,
- private ParentFinder $parentFinder
+ private ParentFinder $parentFinder,
+ private AutoReplyDetector $autoReplyDetector,
) {
$this->notificator = $notificatorFactory->createByClass(Email::class);
$this->filtersMatcher = new FiltersMatcher();
@@ -146,6 +147,8 @@ class DefaultImporter implements Importer
return $duplicate;
}
+ $email->setIsAutoReply($this->autoReplyDetector->detect($message));
+
$this->processDeliveryDate($parser, $message, $email);
if (!$email->getDateSent()) {
diff --git a/application/Espo/Entities/Email.php b/application/Espo/Entities/Email.php
index c069bd744e..01cee0acad 100644
--- a/application/Espo/Entities/Email.php
+++ b/application/Espo/Entities/Email.php
@@ -893,4 +893,14 @@ class Email extends Entity
{
return $this->set('isReplied', $isReplied);
}
+
+ public function isAutoReply(): bool
+ {
+ return $this->get('isAutoReply');
+ }
+
+ public function setIsAutoReply(bool $isAutoReply = true): self
+ {
+ return $this->set('isAutoReply', $isAutoReply);
+ }
}
diff --git a/application/Espo/Modules/Crm/Entities/CaseObj.php b/application/Espo/Modules/Crm/Entities/CaseObj.php
index d9b1318fe1..b60e43c942 100644
--- a/application/Espo/Modules/Crm/Entities/CaseObj.php
+++ b/application/Espo/Modules/Crm/Entities/CaseObj.php
@@ -50,6 +50,11 @@ class CaseObj extends Entity
protected $entityType = 'Case';
+ public function getNumber(): ?int
+ {
+ return $this->get('number');
+ }
+
public function setName(?string $name): self
{
return $this->set(Field::NAME, $name);
diff --git a/application/Espo/Resources/i18n/en_US/Email.json b/application/Espo/Resources/i18n/en_US/Email.json
index e0e43b4ef9..af56d438e7 100644
--- a/application/Espo/Resources/i18n/en_US/Email.json
+++ b/application/Espo/Resources/i18n/en_US/Email.json
@@ -61,7 +61,8 @@
"icsEventDateStart": "ICS Event Date Start",
"groupFolder": "Group Folder",
"groupStatusFolder": "Group Status Folder",
- "sendAt": "Send At"
+ "sendAt": "Send At",
+ "isAutoReply": "Is Auto-Reply"
},
"links": {
"replied": "Replied",
diff --git a/application/Espo/Resources/metadata/entityDefs/Email.json b/application/Espo/Resources/metadata/entityDefs/Email.json
index 21ef4e2b0d..717efca43a 100644
--- a/application/Espo/Resources/metadata/entityDefs/Email.json
+++ b/application/Espo/Resources/metadata/entityDefs/Email.json
@@ -443,6 +443,11 @@
"Espo\\Classes\\FieldValidators\\Email\\SendAt\\Future"
]
},
+ "isAutoReply": {
+ "type": "bool",
+ "readOnly": true,
+ "fieldManagerParamList": []
+ },
"createdAt": {
"type": "datetime",
"readOnly": true,
diff --git a/application/Espo/Resources/metadata/selectDefs/Email.json b/application/Espo/Resources/metadata/selectDefs/Email.json
index 206caf0e92..b829c0ed1d 100644
--- a/application/Espo/Resources/metadata/selectDefs/Email.json
+++ b/application/Espo/Resources/metadata/selectDefs/Email.json
@@ -31,7 +31,11 @@
"textFilterClassName": "Espo\\Classes\\Select\\Email\\TextFilter",
"textFilterUseContainsAttributeList": ["name"],
"selectAttributesDependencyMap": {
- "subject": ["name"],
+ "subject": [
+ "name",
+ "isAutoReply",
+ "hasAttachment"
+ ],
"personStringData": ["fromString", "fromEmailAddressId"],
"replyToName": ["replyToString"]
}
diff --git a/client/res/templates/email/fields/subject/list-link.tpl b/client/res/templates/email/fields/subject/list-link.tpl
index a06de148bd..935d12d368 100644
--- a/client/res/templates/email/fields/subject/list-link.tpl
+++ b/client/res/templates/email/fields/subject/list-link.tpl
@@ -7,17 +7,25 @@
title="{{value}}"
>{{value}}
- {{#if hasAttachment}}
-
-
-
+ {{#if hasIcon}}
+
+ {{#if hasAttachment}}
+
+ {{/if}}
+ {{#if isAutoReply}}
+
+ {{/if}}
+
{{/if}}
diff --git a/client/src/views/email/fields/subject.js b/client/src/views/email/fields/subject.js
index 77629552fa..5c3a817797 100644
--- a/client/src/views/email/fields/subject.js
+++ b/client/src/views/email/fields/subject.js
@@ -39,6 +39,17 @@ class EmailSubjectFieldView extends VarcharFieldView {
data.isImportant = this.model.has('isImportant') && this.model.get('isImportant');
data.hasAttachment = this.model.has('hasAttachment') && this.model.get('hasAttachment');
data.isReplied = this.model.has('isReplied') && this.model.get('isReplied');
+ data.isAutoReply = this.model.has('isAutoReply') && this.model.attributes.isAutoReply;
+
+ data.hasIcon = data.hasAttachment || data.isAutoReply;
+
+ if (data.hasIcon) {
+ data.iconCount = 1;
+
+ if (data.hasAttachment && data.isAutoReply) {
+ data.iconCount = 2;
+ }
+ }
data.inTrash = this.model.attributes.groupFolderId ?
this.model.attributes.groupStatusFolder === 'Trash' :
@@ -88,6 +99,7 @@ class EmailSubjectFieldView extends VarcharFieldView {
'hasAttachment',
'inTrash',
'groupStatusFolder',
+ 'isAutoReply',
];
}
diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less
index 0d4af13d80..3c739a5bd6 100644
--- a/frontend/less/espo/custom.less
+++ b/frontend/less/espo/custom.less
@@ -374,6 +374,18 @@ div.list-expanded > ul > li > div.expanded-row {
display: inline-block;
}
}
+
+ &:has(.list-icon-container[data-icon-count="2"]) {
+ > span {
+ > span:first-child {
+ width: calc(100% - var(--32px));
+ }
+
+ > span.list-icon-container {
+ width: var(--32px);
+ }
+ }
+ }
}
}
}