dev
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Zend Framework (http://framework.zend.com/)
|
||||
*
|
||||
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
||||
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
||||
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||||
*/
|
||||
|
||||
namespace Espo\Core\Mail\Mail\Header;
|
||||
|
||||
use Zend\Mime\Mime;
|
||||
|
||||
class XQueueItemId extends \Zend\Mail\Header\GenericHeader
|
||||
{
|
||||
protected $fieldName = 'X-QueueItemId';
|
||||
|
||||
protected $id = null;
|
||||
|
||||
public function getFieldName()
|
||||
{
|
||||
return $this->fieldName;
|
||||
}
|
||||
|
||||
public function setFieldName($value)
|
||||
{
|
||||
}
|
||||
|
||||
public function setEncoding($encoding)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getEncoding()
|
||||
{
|
||||
return 'ASCII';
|
||||
}
|
||||
|
||||
public function toString()
|
||||
{
|
||||
return $this->fieldName . ': ' . $this->getFieldValue();
|
||||
}
|
||||
|
||||
public function getFieldValue($format = \Zend\Mail\Header\HeaderInterface::FORMAT_RAW)
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,9 @@ class Sender
|
||||
|
||||
public function send(Email $email, $params = array(), &$message = null, $attachmetList = [])
|
||||
{
|
||||
$message = new Message();
|
||||
if (!$message) {
|
||||
$message = new Message();
|
||||
}
|
||||
$config = $this->config;
|
||||
$params = $this->params + $params;
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ return array (
|
||||
'b2cMode' => false,
|
||||
'restrictedMode' => false,
|
||||
'theme' => 'Espo',
|
||||
'massEmailMaxPerHourCount' => 100,
|
||||
'isInstalled' => false,
|
||||
);
|
||||
|
||||
|
||||
@@ -116,7 +116,8 @@ return array (
|
||||
'ldapTryUsernameSplit',
|
||||
'ldapOptReferrals',
|
||||
'ldapCreateEspoUser',
|
||||
'maxEmailAccountCount'
|
||||
'maxEmailAccountCount',
|
||||
'massEmailMaxPerHourCount'
|
||||
),
|
||||
'isInstalled' => false,
|
||||
);
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"campaign": "Campaign",
|
||||
"parent": "Target",
|
||||
"object": "Object",
|
||||
"application": "Application"
|
||||
"application": "Application",
|
||||
"queueItem": "Queue Item"
|
||||
},
|
||||
"options": {
|
||||
"action": {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"status": "Status",
|
||||
"target": "Target",
|
||||
"sentAt": "Date Sent",
|
||||
"attemptCount": "Attempts"
|
||||
"attemptCount": "Attempts",
|
||||
"emailAddress": "Email Address"
|
||||
},
|
||||
"links": {
|
||||
"target": "Target",
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
},
|
||||
"object": {
|
||||
"type": "linkParent"
|
||||
},
|
||||
"queueItem": {
|
||||
"type": "link"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
@@ -56,6 +59,11 @@
|
||||
"entity": "Campaign",
|
||||
"foreign": "campaignLogRecords"
|
||||
},
|
||||
"queueItem": {
|
||||
"type": "belongsTo",
|
||||
"entity": "EmailQueueItem",
|
||||
"noJoin": true
|
||||
},
|
||||
"parent": {
|
||||
"type": "belongsToParent",
|
||||
"entityList": ["Account", "Contact", "Lead", "Opportunity", "User"]
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
"sentAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "varchar",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -95,5 +95,53 @@ class Campaign extends \Espo\Services\Record
|
||||
}
|
||||
}
|
||||
|
||||
public function logSent($campaignId, $queueItemId = null, Entity $target, Entity $emailOrEmailTemplate = null, $emailAddress, $actionDate = null)
|
||||
{
|
||||
if (empty($actionDate)) {
|
||||
$actionDate = date('Y-m-d H:i:s');
|
||||
}
|
||||
$logRecord = $this->getEntityManager()->getEntity('CampaignLogRecord');
|
||||
$logRecord->set(array(
|
||||
'campaignId' => $campaignId,
|
||||
'actionDate' => $actionDate,
|
||||
'parentId' => $target->id,
|
||||
'parentType' => $target->getEntityType(),
|
||||
'action' => 'Sent',
|
||||
'stringData' => $emailAddress,
|
||||
'queueItemId' => $queueItemId
|
||||
));
|
||||
|
||||
if ($emailOrEmailTemplate) {
|
||||
$logRecord->set(array(
|
||||
'objectId' => $emailOrEmailTemplate->id,
|
||||
'objectType' => $emailOrEmailTemplate->getEntityType()
|
||||
));
|
||||
}
|
||||
$this->getEntityManager()->saveEntity($logRecord);
|
||||
}
|
||||
|
||||
public function logBounced($campaignId, $queueItemId = null, Entity $target, $emailAddress, $isHard = false, $actionDate = null)
|
||||
{
|
||||
if (empty($actionDate)) {
|
||||
$actionDate = date('Y-m-d H:i:s');
|
||||
}
|
||||
$logRecord = $this->getEntityManager()->getEntity('CampaignLogRecord');
|
||||
$logRecord->set(array(
|
||||
'campaignId' => $campaignId,
|
||||
'actionDate' => $actionDate,
|
||||
'parentId' => $target->id,
|
||||
'parentType' => $target->getEntityType(),
|
||||
'action' => 'Bounced',
|
||||
'stringData' => $emailAddress,
|
||||
'queueItemId' => $queueItemId
|
||||
));
|
||||
if ($isHard) {
|
||||
$logRecord->set('stringAdditionalData', 'Hard');
|
||||
} else {
|
||||
$logRecord->set('stringAdditionalData', 'Soft');
|
||||
}
|
||||
$this->getEntityManager()->saveEntity($logRecord);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ class MassEmail extends \Espo\Services\Record
|
||||
{
|
||||
const MAX_ATTEMPT_COUNT = 3;
|
||||
|
||||
const MAX_PER_HOUR_COUNT = 100;
|
||||
|
||||
private $campaignService = null;
|
||||
|
||||
private $emailTemplateService = null;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$this->dependencies[] = 'container';
|
||||
@@ -138,10 +144,25 @@ class MassEmail extends \Espo\Services\Record
|
||||
|
||||
public function processSending(Entity $massEmail)
|
||||
{
|
||||
$threshold = new \DateTime();
|
||||
$threshold->modify('-1 hour');
|
||||
$sentLastHourCount = $this->getEntityManager()->getRepository('EmailQueueItem')->where(array(
|
||||
'status' => 'Sent',
|
||||
'sentAt>' => $threshold->format('Y-m-d H:i:s')
|
||||
))->count();
|
||||
|
||||
$maxBatchSize = $this->getConfig()->get('massEmailMaxPerHourCount', self::MAX_PER_HOUR_COUNT);
|
||||
|
||||
if ($sentLastHourCount >= $maxBatchSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
$maxBatchSize = $maxBatchSize - $sentLastHourCount;
|
||||
|
||||
$queueItemList = $this->getEntityManager()->getRepository('EmailQueueItem')->where(array(
|
||||
'status' => 'Pending',
|
||||
'massEmailId' => $massEmail->id
|
||||
))->find();
|
||||
))->limit(0, $maxBatchSize)->find();
|
||||
|
||||
$templateId = $massEmail->get('emailTemplateId');
|
||||
if (!$templateId) {
|
||||
@@ -149,6 +170,11 @@ class MassEmail extends \Espo\Services\Record
|
||||
return;
|
||||
}
|
||||
|
||||
$campaign = null;
|
||||
$campaignId = $massEmail->get('campaignId');
|
||||
if ($campaignId) {
|
||||
$campaign = $this->getEntityManager()->getEntity('Campaign', $campaignId);
|
||||
}
|
||||
$emailTemplate = $this->getEntityManager()->getEntity('EmailTemplate', $templateId);
|
||||
if (!$emailTemplate) {
|
||||
$this->setFailed($massEmail);
|
||||
@@ -157,7 +183,7 @@ class MassEmail extends \Espo\Services\Record
|
||||
$attachmetList = $emailTemplate->get('attachmets');
|
||||
|
||||
foreach ($queueItemList as $queueItem) {
|
||||
$this->sendQueueItem($queueItem, $massEmail, $emailTemplate, $attachmetList);
|
||||
$this->sendQueueItem($queueItem, $massEmail, $emailTemplate, $attachmetList, $campaign);
|
||||
}
|
||||
|
||||
$countLeft = $this->getEntityManager()->getRepository('EmailQueueItem')->where(array(
|
||||
@@ -170,7 +196,7 @@ class MassEmail extends \Espo\Services\Record
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendQueueItem(Entity $queueItem, Entity $massEmail, Entity $emailTemplate, $attachmetList = [])
|
||||
protected function sendQueueItem(Entity $queueItem, Entity $massEmail, Entity $emailTemplate, $attachmetList = [], $campaign = null)
|
||||
{
|
||||
$target = $this->getEntityManager()->getEntity($queueItem->get('targetType'), $queueItem->get('targetId'));
|
||||
if (!$target || !$target->id || !$target->get('emailAddress')) {
|
||||
@@ -214,12 +240,30 @@ class MassEmail extends \Espo\Services\Record
|
||||
$attemptCount++;
|
||||
$queueItem->set('attemptCount', $attemptCount);
|
||||
|
||||
$message = false;
|
||||
$message = new \Zend\Mail\Message();
|
||||
|
||||
$header = new \Espo\Core\Mail\Mail\Header\XQueueItemId();
|
||||
$header->setId($queueItem->id);
|
||||
$message->getHeaders()->addHeader($header);
|
||||
|
||||
$this->getMailSender()->useGlobal()->send($email, $params, $message, $attachmetList);
|
||||
|
||||
$emailObject = $emailTemplate;
|
||||
if ($massEmail->get('storeSentEmails')) {
|
||||
$this->getEntityManager()->saveEntity($email);
|
||||
$emailObject = $email;
|
||||
}
|
||||
|
||||
$queueItem->set('emailAddress', $target->get('emailAddress'));
|
||||
|
||||
$queueItem->set('status', 'Sent');
|
||||
$queueItem->set('sentAt', date('Y-m-d H:i:s'));
|
||||
$this->getEntityManager()->saveEntity($queueItem);
|
||||
|
||||
if ($campaign) {
|
||||
$this->getCampaignService()->logSent($campaign->id, $queueItem->id, $target, $emailObject, $target->get('emailAddress'));
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if ($queueItem->get('attemptCount') >= self::MAX_ATTEMPT_COUNT) {
|
||||
$queueItem->set('status', 'Failed');
|
||||
@@ -239,5 +283,13 @@ class MassEmail extends \Espo\Services\Record
|
||||
}
|
||||
return $this->emailTemplateService;
|
||||
}
|
||||
|
||||
protected function getCampaignService()
|
||||
{
|
||||
if (!$this->campaignService) {
|
||||
$this->campaignService = $this->getServiceFactory()->create('Campaign');
|
||||
}
|
||||
return $this->campaignService;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +308,9 @@ class RDB extends \Espo\ORM\Repository
|
||||
$this->handleSelectParams($params);
|
||||
|
||||
$params = $this->getSelectParams($params);
|
||||
return $this->getMapper()->count($this->seed, $params);
|
||||
$count = $this->getMapper()->count($this->seed, $params);
|
||||
$this->reset();
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function max($field)
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
"displayListViewRecordCount": "Display Total Count (on List View)",
|
||||
"theme": "Theme",
|
||||
"userThemesDisabled": "Disable User Themes",
|
||||
"emailMessageMaxSize": "Email Max Size (Mb)"
|
||||
"emailMessageMaxSize": "Email Max Size (Mb)",
|
||||
"massEmailMaxPerHourCount": "Max count of emails sent per hour"
|
||||
},
|
||||
"options": {
|
||||
"weekStart": {
|
||||
@@ -77,6 +78,7 @@
|
||||
"In-app Notifications": "In-app Notifications",
|
||||
"Email Notifications": "Email Notifications",
|
||||
"Currency Settings": "Currency Settings",
|
||||
"Currency Rates": "Currency Rates"
|
||||
"Currency Rates": "Currency Rates",
|
||||
"Mass Email"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,11 @@
|
||||
[{"name": "outboundEmailFromAddress"}, {"name": "outboundEmailIsShared"}],
|
||||
[{"name": "outboundEmailFromName"}, false]
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Mass Email",
|
||||
"rows": [
|
||||
[{"name": "massEmailMaxPerHourCount"}, false]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -252,6 +252,10 @@
|
||||
"type": "float",
|
||||
"min": 0,
|
||||
"tooltip": true
|
||||
},
|
||||
"massEmailMaxPerHourCount": {
|
||||
"type": "int",
|
||||
"min": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ class InboundEmail extends \Espo\Services\Record
|
||||
{
|
||||
protected $internalFields = array('password');
|
||||
|
||||
private $campaignService = null;
|
||||
|
||||
const PORTION_LIMIT = 20;
|
||||
|
||||
public function createEntity($data)
|
||||
@@ -255,6 +257,11 @@ class InboundEmail extends \Espo\Services\Record
|
||||
$fromString = $message->from;
|
||||
if (preg_match('/MAILER-DAEMON|POSTMASTER/i', $fromString)) {
|
||||
$toSkip = true;
|
||||
try {
|
||||
$this->processBouncedMessage($message);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('InboundEmail '.$emailAccount->id.' (Process Bounced Message: [' . $e->getCode() . '] ' .$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$toSkip) {
|
||||
@@ -551,5 +558,52 @@ class InboundEmail extends \Espo\Services\Record
|
||||
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
protected function processBouncedMessage(\Zend\Mail\Message $message)
|
||||
{
|
||||
$content = $message->getContent();
|
||||
echo "bounced\n";
|
||||
|
||||
$isHard = false;
|
||||
if (preg_match('/permanent[ ]*error/', $content)) {
|
||||
$isHard = true;
|
||||
}
|
||||
if (preg_match('/X-QueueItemId: [a-z0-9\-]*/', $content, $m)) {
|
||||
$queueItemId = preg_split('/X-QueueItemId: /', $m[0], -1, \PREG_SPLIT_NO_EMPTY);
|
||||
if (!$queueItemId) return;
|
||||
echo $queueItemId . "\n";
|
||||
|
||||
$queueItem = $this->getEntityManager()->getEntity('EmailQueueItem', $queueItemId);
|
||||
|
||||
if (!$queueItem) return;
|
||||
|
||||
$campaignId = $queueItem->get('campaignId');
|
||||
|
||||
$targetType = $queueItem->get('targetType');
|
||||
$targetId = $queueItem->get('targetId');
|
||||
$target = $this->getEntityManager()->getEntity($targetType, $targetId);
|
||||
|
||||
$emailAddress = $queueItem->get('emailAddress');
|
||||
|
||||
if ($isHard && $emailAddress) {
|
||||
$emailAddressEntity = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($emailAddress);
|
||||
$emailAddressEntity->set('invalid', true);
|
||||
$this->getEntityManager()->saveEntity($emailAddressEntity);
|
||||
}
|
||||
|
||||
if ($campaignId && $target) {
|
||||
$this->getCampaignService()->logBounced($campaignId, $queueItemId, $target, $emailAddress, $isHard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCampaignService()
|
||||
{
|
||||
if (!$this->campaignService) {
|
||||
$this->campaignService = $this->getServiceFactory()->create('Campaign');
|
||||
}
|
||||
return $this->campaignService;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,17 @@ Espo.define('Crm:Views.CampaignLogRecord.Fields.Data', 'Views.Fields.Base', func
|
||||
|
||||
switch (action) {
|
||||
case 'Sent':
|
||||
return this.model.get('stringData');
|
||||
if (this.model.get('objectId') && this.model.get('objectType') && this.model.get('objectName')) {
|
||||
return '<a href="#'+this.model.get('objectType')+'/view/'+this.model.get('objectId')+'">'+this.model.get('objectName')+'</a>';
|
||||
}
|
||||
return this.model.get('stringData') || '';
|
||||
case 'Clicked':
|
||||
return '<span class="text-success">' + this.model.get('stringData') + '</span>';
|
||||
if (this.model.get('objectId') && this.model.get('objectType') && this.model.get('objectName')) {
|
||||
return '<a href="#'+this.model.get('objectType')+'/view/'+this.model.get('objectId')+'">'+this.model.get('objectName')+'</a>';
|
||||
}
|
||||
return '<span>' + (this.model.get('stringData') || '') + '</span>';
|
||||
case 'Opted Out':
|
||||
return '<span class="text-muted">' + this.model.get('stringData') + '</span>';
|
||||
return '<s>' + this.model.get('stringData') + '</s>';
|
||||
case 'Bounced':
|
||||
var emailAddress = this.model.get('stringData');
|
||||
var type = this.model.get('stringAdditionalData');
|
||||
|
||||
Reference in New Issue
Block a user