vendor/uvdesk/mailbox-component/Services/MailboxService.php line 108

Open in your IDE?
  1. <?php
  2. namespace Webkul\UVDesk\MailboxBundle\Services;
  3. use PhpMimeMailParser\Parser as EmailParser;
  4. use Symfony\Component\Yaml\Yaml;
  5. use Doctrine\ORM\EntityManagerInterface;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\HttpFoundation\RequestStack;
  9. use Webkul\UVDesk\CoreFrameworkBundle\Entity\User;
  10. use Symfony\Component\EventDispatcher\GenericEvent;
  11. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Ticket;
  12. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Thread;
  13. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Website;
  14. use Webkul\UVDesk\MailboxBundle\Utils\Mailbox\Mailbox;
  15. use Webkul\UVDesk\CoreFrameworkBundle\Utils\HTMLFilter;
  16. use Webkul\UVDesk\CoreFrameworkBundle\Entity\SupportRole;
  17. use Webkul\UVDesk\CoreFrameworkBundle\Utils\TokenGenerator;
  18. use Webkul\UVDesk\MailboxBundle\Utils\MailboxConfiguration;
  19. use Symfony\Component\DependencyInjection\ContainerInterface;
  20. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events as CoreWorkflowEvents;
  21. use Webkul\UVDesk\MailboxBundle\Utils\Imap\Configuration as ImapConfiguration;
  22. use Webkul\UVDesk\CoreFrameworkBundle\SwiftMailer\SwiftMailer as SwiftMailerService;
  23. use Webkul\UVDesk\MailboxBundle\Workflow\Events as MaibloxWorkflowEvents;
  24. class MailboxService
  25. {
  26.     const PATH_TO_CONFIG '/config/packages/uvdesk_mailbox.yaml';
  27.     private $mailboxCollection = [];
  28.     public function __construct(ContainerInterface $containerRequestStack $requestStackEntityManagerInterface $entityManagerSwiftMailerService $swiftMailer)
  29.     {
  30.         $this->container $container;
  31.         $this->requestStack $requestStack;
  32.         $this->entityManager $entityManager;
  33.         $this->swiftMailer $swiftMailer;
  34.     }
  35.     public function getPathToConfigurationFile()
  36.     {
  37.         return $this->container->get('kernel')->getProjectDir() . self::PATH_TO_CONFIG;
  38.     }
  39.     public function createConfiguration($params)
  40.     {
  41.         $configuration = new MailboxConfigurations\MailboxConfiguration($params);
  42.         return $configuration ?? null;
  43.     }
  44.     public function parseMailboxConfigurations(bool $ignoreInvalidAttributes false
  45.     {
  46.         $path $this->getPathToConfigurationFile();
  47.         if (!file_exists($path)) {
  48.             throw new \Exception("File '$path' not found.");
  49.         }
  50.         // Read configurations from package config.
  51.         $mailboxConfiguration = new MailboxConfiguration();
  52.         $swiftmailerService $this->swiftMailer;
  53.         $swiftmailerConfigurations $swiftmailerService->parseSwiftMailerConfigurations();
  54.         foreach (Yaml::parse(file_get_contents($path))['uvdesk_mailbox']['mailboxes'] ?? [] as $id => $params) {
  55.             // Swiftmailer Configuration
  56.             $swiftmailerConfiguration null;
  57.             foreach ($swiftmailerConfigurations as $configuration) {
  58.                 if ($configuration->getId() == $params['smtp_server']['mailer_id']) {
  59.                     $swiftmailerConfiguration $configuration;
  60.                     break;
  61.                 }
  62.             }
  63.             
  64.             // IMAP Configuration
  65.             ($imapConfiguration ImapConfiguration::guessTransportDefinition($params['imap_server']['host']))
  66.                 ->setUsername($params['imap_server']['username'])
  67.                 ->setPassword($params['imap_server']['password']);
  68.             // Mailbox Configuration
  69.             ($mailbox = new Mailbox($id))
  70.                 ->setName($params['name'])
  71.                 ->setIsEnabled($params['enabled'])
  72.                 ->setIsDeleted(empty($params['deleted']) ? false $params['deleted'])
  73.                 ->setImapConfiguration($imapConfiguration);
  74.             
  75.             if (!empty($swiftmailerConfiguration)) {
  76.                 $mailbox->setSwiftMailerConfiguration($swiftmailerConfiguration);
  77.             } else if (!empty($params['smtp_server']['mailer_id']) && true === $ignoreInvalidAttributes) {
  78.                 $mailbox->setSwiftMailerConfiguration($swiftmailerService->createConfiguration('smtp'$params['smtp_server']['mailer_id']));
  79.             }
  80.             $mailboxConfiguration->addMailbox($mailbox);
  81.         }
  82.         return $mailboxConfiguration;
  83.     }
  84.     private function getLoadedEmailContentParser($emailContents null$cacheContent true): ?EmailParser
  85.     {
  86.         if (empty($emailContents)) {
  87.             return $this->emailParser ?? null;
  88.         }
  89.         $emailParser = new EmailParser();
  90.         $emailParser
  91.             ->setText($emailContents)
  92.         ;
  93.         if ($cacheContent) {
  94.             $this->emailParser $emailParser;
  95.         }
  96.         return $emailParser;
  97.     }
  98.     private function getRegisteredMailboxes()
  99.     {
  100.         if (empty($this->mailboxCollection)) {
  101.             $this->mailboxCollection array_map(function ($mailboxId) {
  102.                 return $this->container->getParameter("uvdesk.mailboxes.$mailboxId");
  103.             }, $this->container->getParameter('uvdesk.mailboxes'));
  104.         }
  105.         return $this->mailboxCollection;
  106.     }
  107.     public function getRegisteredMailboxesById()
  108.     {
  109.         // Fetch existing content in file
  110.         $filePath $this->getPathToConfigurationFile();
  111.         $file_content file_get_contents($filePath);
  112.         // Convert yaml file content into array and merge existing mailbox and new mailbox
  113.         $file_content_array Yaml::parse($file_content6);
  114.         if ($file_content_array['uvdesk_mailbox']['mailboxes']) {
  115.             foreach ($file_content_array['uvdesk_mailbox']['mailboxes'] as $key => $value) {
  116.                 $value['mailbox_id'] = $key;
  117.                 $mailboxCollection[] = $value;
  118.             }
  119.         }
  120.         
  121.         return $mailboxCollection ?? [];
  122.     }
  123.     public function getEmailAddresses($collection)
  124.     {
  125.         $formattedCollection array_map(function ($emailAddress) {
  126.             if (filter_var($emailAddress['address'], FILTER_VALIDATE_EMAIL)) {
  127.                 return $emailAddress['address'];
  128.             }
  129.             return null;
  130.         }, (array) $collection);
  131.         $filteredCollection array_values(array_filter($formattedCollection));
  132.         return count($filteredCollection) == $filteredCollection[0] : $filteredCollection;
  133.     }
  134.     public function getMailboxByEmail($email)
  135.     {
  136.         foreach ($this->getRegisteredMailboxes() as $registeredMailbox) {
  137.             if (strtolower($email) === strtolower($registeredMailbox['imap_server']['username'])) {
  138.                 return $registeredMailbox;
  139.             }
  140.         }
  141.         throw new \Exception("No mailbox found for email '$email'");
  142.     }
  143.     
  144.     private function searchticketSubjectRefrence($senderEmail$messageSubject) {
  145.         
  146.         // Search Criteria: Find ticket based on subject
  147.         if (!empty($senderEmail) && !empty($messageSubject)) {
  148.             $threadRepository $this->entityManager->getRepository(Thread::class);
  149.             $ticket $threadRepository->findTicketBySubject($senderEmail$messageSubject);
  150.             if ($ticket  != null) {
  151.                 return $ticket;
  152.             }
  153.         }
  154.         return null;
  155.     }
  156.     private function searchExistingTickets(array $criterias = [])
  157.     {
  158.         if (empty($criterias)) {
  159.             return null;
  160.         }
  161.         $ticketRepository $this->entityManager->getRepository(Ticket::class);
  162.         $threadRepository $this->entityManager->getRepository(Thread::class);
  163.         foreach ($criterias as $criteria => $criteriaValue) {
  164.             if (empty($criteriaValue)) {
  165.                 continue;
  166.             }
  167.             switch ($criteria) {
  168.                 case 'messageId':
  169.                     // Search Criteria 1: Find ticket by unique message id
  170.                     $ticket $ticketRepository->findOneByReferenceIds($criteriaValue);
  171.                     if (!empty($ticket)) {
  172.                         return $ticket;
  173.                     } else {
  174.                         $thread $threadRepository->findOneByMessageId($criteriaValue);
  175.         
  176.                         if (!empty($thread)) {
  177.                             return $thread->getTicket();
  178.                         }
  179.                     }
  180.                     break;
  181.                 case 'inReplyTo':
  182.                     // Search Criteria 2: Find ticket based on in-reply-to reference id
  183.                     $repository $this->entityManager->getRepository(Thread::class);
  184.                     $ticket $repository->findThreadByRefrenceId($criteriaValue);
  185.                     if (!empty($ticket)) {
  186.                         return $ticket;
  187.                     } else {
  188.                         $thread $threadRepository->findOneByMessageId($criteriaValue);
  189.         
  190.                         if (!empty($thread)) {
  191.                             return $thread->getTicket();
  192.                         }
  193.                     }
  194.                     break;
  195.                 case 'referenceIds':
  196.                     // Search Criteria 3: Find ticket based on reference id
  197.                     // Break references into ind. message id collection, and iteratively 
  198.                     // search for existing threads for these message ids.
  199.                     $referenceIds explode(' '$criteriaValue);
  200.                     foreach ($referenceIds as $messageId) {
  201.                         $thread $threadRepository->findOneByMessageId($messageId);
  202.                         if (!empty($thread)) {
  203.                             return $thread->getTicket();
  204.                         }
  205.                     }
  206.                     break;
  207.                 default:
  208.                     break;
  209.             }
  210.         }
  211.         return null;
  212.     }
  213.     private function prepareResolvedEmailHeaders(EmailParser $emailParser): array
  214.     {
  215.         // Email headers with all sender/recipients details
  216.         $emailHeaders = [
  217.             'from' => $emailParser->getHeader('from') != false $emailParser->getHeader('from') : null
  218.             'reply-to' => $emailParser->getHeader('reply-to') != false $emailParser->getHeader('reply-to') : null
  219.             'to' => $emailParser->getHeader('to') != false $emailParser->getHeader('to') : null
  220.             'cc' => $emailParser->getHeader('cc') != false $emailParser->getHeader('cc') : null
  221.             'bcc' => $emailParser->getHeader('bcc') != false $emailParser->getHeader('bcc') : null
  222.             'x-forwarded-to' => $emailParser->getHeader('x-forwarded-to') != false $emailParser->getHeader('x-forwarded-to') : null
  223.             'delivered-to' => $emailParser->getHeader('delivered-to') != false $emailParser->getHeader('delivered-to') : null
  224.         ];
  225.     
  226.         // If 'from' header is empty, use 'sender' header if provided instead
  227.         if (empty($emailHeaders['from']) && $emailParser->getHeader('sender') != false) {
  228.             $emailHeaders['from'] = $emailParser->getHeader('sender');
  229.         }
  230.         
  231.         // Resolve & map only email addresses from email headers
  232.         $resolvedEmailHeaders = [];
  233.     
  234.         foreach ($emailHeaders as $headerName => $headerContent) {
  235.             $resolvedEmailHeaders[$headerName] = null;
  236.             
  237.             if (!empty($headerContent)) {
  238.                 $parsedEmailAddresses mailparse_rfc822_parse_addresses($headerContent);
  239.     
  240.                 $emailHeaders[$headerName] = $parsedEmailAddresses;
  241.                 $resolvedEmailHeaders[$headerName] = $this->getEmailAddresses($parsedEmailAddresses);
  242.             }
  243.         }
  244.         return [$emailHeaders$resolvedEmailHeaders];
  245.     }
  246.     
  247.     public function processMail($emailContents)
  248.     {
  249.         $emailParser $this->getLoadedEmailContentParser($emailContents);
  250.         list($emailHeaders$resolvedEmailHeaders) = $this->prepareResolvedEmailHeaders($emailParser);
  251.         // Skip email processing if email is an auto-forwarded message to prevent infinite loop.
  252.         if (empty($resolvedEmailHeaders['from'])) {
  253.             // Skip email processing if no to-emails are specified
  254.             return [
  255.                 'message' => "No sender email addresses were found while processing contents of email."
  256.                 'content' => [], 
  257.             ];
  258.         } else if (empty($resolvedEmailHeaders['to']) && empty($resolvedEmailHeaders['delivered-to']) && empty($resolvedEmailHeaders['cc'])) {
  259.             // Skip email processing if no recipient emails are specified
  260.             return [
  261.                 'message' => "No recipient email addresses were found while processing contents of email."
  262.                 'content' => [
  263.                     'from' => !empty($resolvedEmailHeaders['from']) ? $resolvedEmailHeaders['from'] : null
  264.                 ], 
  265.             ];
  266.         } else {
  267.             // Skip email if it is auto-generated to prevent looping of emails
  268.             if ($emailParser->getHeader('precedence') || $emailParser->getHeader('x-autoreply') || $emailParser->getHeader('x-autorespond') || 'auto-replied' == $emailParser->getHeader('auto-submitted')) {
  269.                 return [
  270.                     'message' => "Received an auto-forwarded email which can lead to possible infinite loop of email exchanges. Skipping email from further processing."
  271.                     'content' => [
  272.                         'from' => $resolvedEmailHeaders['from'] ?? null
  273.                     ], 
  274.                 ];
  275.             }
  276.             $website $this->entityManager->getRepository(Website::class)->findOneByCode('knowledgebase');
  277.             // Skip email if sender email address is in block list
  278.             if ($this->container->get('ticket.service')->isEmailBlocked($resolvedEmailHeaders['from'], $website)) {
  279.                 return [
  280.                     'message' => "Received email where the sender email address is present in the block list. Skipping this email from further processing."
  281.                     'content' => [
  282.                         'from' => $resolvedEmailHeaders['from'], 
  283.                     ], 
  284.                 ];
  285.             }
  286.             // Check for self-referencing
  287.             // 1. Skip email processing if a mailbox is configured by the sender's address
  288.             try {
  289.                 $this->getMailboxByEmail($resolvedEmailHeaders['from']);
  290.                 return [
  291.                     'message' => "Received a self-referencing email where the sender email address matches one of the configured mailbox address. Skipping email from further processing."
  292.                     'content' => [
  293.                         'from' => $resolvedEmailHeaders['from'], 
  294.                     ], 
  295.                 ];
  296.             } catch (\Exception $e) { /* No mailboxes found */ }
  297.             // 2. Skip email processing if a mailbox is configured by the reply-to email address
  298.             try {
  299.                 if (!empty($resolvedEmailHeaders['reply-to'])) {
  300.                     $this->getMailboxByEmail($resolvedEmailHeaders['reply-to']);
  301.                     return [
  302.                         'message' => "Received a self-referencing email where the reply-to email address matches one of the configured mailbox address. Skipping email from further processing."
  303.                         'content' => [
  304.                             'from' => $resolvedEmailHeaders['reply-to'], 
  305.                         ], 
  306.                     ];
  307.                 }
  308.             } catch (\Exception $e) { /* No mailboxes found */ }
  309.         }
  310.         // Trigger email recieved event
  311.         $event = new MaibloxWorkflowEvents\Email\EmailRecieved();
  312.         $event
  313.             ->setEmailHeaders($emailHeaders)
  314.             ->setResolvedEmailHeaders($resolvedEmailHeaders)
  315.         ;
  316.         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  317.         $emailHeaders $event->getEmailHeaders();
  318.         $resolvedEmailHeaders $event->getResolvedEmailHeaders();
  319.         $senderEmailAddress $resolvedEmailHeaders['from'];
  320.         $senderName trim(current(explode('@'$emailHeaders['from'][0]['display'])));
  321.         $mailboxEmail null;
  322.         
  323.         $mailboxEmailCandidates array_merge((array) $resolvedEmailHeaders['to'], (array) $resolvedEmailHeaders['delivered-to'], (array) $resolvedEmailHeaders['cc']);
  324.         $mailboxEmailCandidates array_values(array_unique(array_filter($mailboxEmailCandidates)));
  325.         foreach ($mailboxEmailCandidates as $emailAddress) {
  326.             try {
  327.                 $mailbox $this->getMailboxByEmail($emailAddress);
  328.                 if (!empty($mailbox)) {
  329.                     $mailboxEmail $emailAddress;
  330.                     break;
  331.                 }
  332.             } catch (\Exception $e) { /* No mailboxes found */ }
  333.         }
  334.         // Process Mail - References
  335.         $mailData = [
  336.             'name' => $senderName
  337.             'from' => $senderEmailAddress
  338.             'role' => 'ROLE_CUSTOMER'
  339.             'source' => 'email'
  340.             'createdBy' => 'customer'
  341.             'mailboxEmail' => $mailboxEmail
  342.             'cc' => $resolvedEmailHeaders['cc'] ?? [], 
  343.             'bcc' => $resolvedEmailHeaders['bcc'] ?? [], 
  344.             'messageId' => $emailParser->getHeader('message-id') != false $emailParser->getHeader('message-id') : null
  345.             'inReplyTo' => $emailParser->getHeader('in-reply-to') != false htmlspecialchars_decode($emailParser->getHeader('in-reply-to')) : null
  346.             'referenceIds' => $emailParser->getHeader('references') != false htmlspecialchars_decode($emailParser->getHeader('references')) : null
  347.             'subject' => $emailParser->getHeader('subject') != false $emailParser->getHeader('subject') : null
  348.             'text' => $emailParser->getMessageBody('text'), 
  349.             'htmlEmbedded' => $emailParser->getMessageBody('htmlEmbedded'), 
  350.             'attachments' => $emailParser->getAttachments(), 
  351.         ];
  352.         // Format message content
  353.         $htmlFilter = new HTMLFilter();
  354.         $mailData['text'] = autolink($htmlFilter->addClassEmailReplyQuote($mailData['text']));
  355.         $mailData['htmlEmbedded'] = autolink($htmlFilter->addClassEmailReplyQuote($mailData['htmlEmbedded']));
  356.         
  357.         $mailData['message'] = !empty($mailData['htmlEmbedded']) ? $mailData['htmlEmbedded'] : $mailData['text'];
  358.         
  359.         // Search for any existing tickets
  360.         $ticket $this->searchExistingTickets([
  361.             'messageId' => $mailData['messageId'],
  362.             'inReplyTo' => $mailData['inReplyTo'],
  363.             'referenceIds' => $mailData['referenceIds'],
  364.             'from' => $mailData['from'],
  365.             'subject' => $mailData['subject'],
  366.         ]);
  367.         if (empty($ticket)) {
  368.             $mailData['threadType'] = 'create';
  369.             $mailData['referenceIds'] = $mailData['messageId'];
  370.             // @TODO: Concatenate two tickets for same customer with same subject depending on settings
  371.             $thread $this->container->get('ticket.service')->createTicket($mailData);
  372.             // Trigger ticket created event
  373.             $event = new CoreWorkflowEvents\Ticket\Create();
  374.             $event
  375.                 ->setTicket($thread->getTicket())
  376.             ;
  377.             $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  378.         } else if (false === $ticket->getIsTrashed() && strtolower($ticket->getStatus()->getCode()) != 'spam' && !empty($mailData['inReplyTo'])) {
  379.             $ticketRef $this->entityManager->getRepository(Ticket::class)->findById($ticket->getId());
  380.             $thread $this->entityManager->getRepository(Thread::class)->findOneByMessageId($mailData['messageId']);
  381.             $referenceIds explode(' '$ticketRef[0]->getReferenceIds());
  382.             if (!empty($thread)) {
  383.                 // Thread with the same message id exists skip process.
  384.                 return [
  385.                     'message' => "The contents of this email has already been processed."
  386.                     'content' => [
  387.                         'from' => !empty($mailData['from']) ? $mailData['from'] : null
  388.                         'thread' => $thread->getId(), 
  389.                         'ticket' => $ticket->getId(), 
  390.                     ], 
  391.                 ];
  392.             }
  393.             if (in_array($mailData['messageId'], $referenceIds)) {
  394.                 // Thread with the same message id exists skip process.
  395.                 return [
  396.                     'message' => "The contents of this email has already been processed."
  397.                     'content' => [
  398.                         'from' => !empty($mailData['from']) ? $mailData['from'] : null
  399.                     ], 
  400.                 ];
  401.             }
  402.             if ($ticket->getCustomer() && $ticket->getCustomer()->getEmail() == $mailData['from']) {
  403.                 // Reply from customer
  404.                 $user $ticket->getCustomer();
  405.                 $mailData['user'] = $user;
  406.                 $userDetails $user->getCustomerInstance()->getPartialDetails();
  407.             } else if ($this->entityManager->getRepository(Ticket::class)->isTicketCollaborator($ticket$mailData['from'])){
  408.                 // Reply from collaborator
  409.                 $user $this->entityManager->getRepository(User::class)->findOneByEmail($mailData['from']);
  410.                 $mailData['user'] = $user;
  411.                 $mailData['createdBy'] = 'collaborator';
  412.                 $userDetails $user->getCustomerInstance()->getPartialDetails();
  413.             } else {
  414.                 $user $this->entityManager->getRepository(User::class)->findOneByEmail($mailData['from']);
  415.                 
  416.                 if (!empty($user) && null != $user->getAgentInstance()) {
  417.                     $mailData['user'] = $user;
  418.                     $mailData['createdBy'] = 'agent';
  419.                     $userDetails $user->getAgentInstance()->getPartialDetails();
  420.                 } else {
  421.                     // Add user as a ticket collaborator
  422.                     if (empty($user)) {
  423.                         // Create a new user instance with customer support role
  424.                         $role $this->entityManager->getRepository(SupportRole::class)->findOneByCode('ROLE_CUSTOMER');
  425.                         $user $this->container->get('user.service')->createUserInstance($mailData['from'], $mailData['name'], $role, [
  426.                             'source' => 'email',
  427.                             'active' => true
  428.                         ]);
  429.                     }
  430.                     $mailData['user'] = $user;
  431.                     $userDetails $user->getCustomerInstance()->getPartialDetails();
  432.                     if (false == $this->entityManager->getRepository(Ticket::class)->isTicketCollaborator($ticket$mailData['from'])) {
  433.                         $ticket->addCollaborator($user);
  434.                         $this->entityManager->persist($ticket);
  435.                         $this->entityManager->flush();
  436.                         $ticket->lastCollaborator $user;
  437.                         
  438.                         $event = new CoreWorkflowEvents\Ticket\Collaborator();
  439.                         $event
  440.                             ->setTicket($ticket)
  441.                         ;
  442.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  443.                     }
  444.                 }
  445.             }
  446.             $mailData['threadType'] = 'reply';
  447.             $mailData['fullname'] = $userDetails['name'];
  448.             
  449.             $thread $this->container->get('ticket.service')->createThread($ticket$mailData);
  450.             
  451.             if ($thread->getThreadType() == 'reply') {
  452.                 if ($thread->getCreatedBy() == 'customer') {
  453.                     $event = new CoreWorkflowEvents\Ticket\CustomerReply();
  454.                     $event
  455.                         ->setTicket($ticket)
  456.                     ;
  457.                 }  else if ($thread->getCreatedBy() == 'collaborator') {
  458.                     $event = new CoreWorkflowEvents\Ticket\CollaboratorReply();
  459.                     $event
  460.                         ->setTicket($ticket)
  461.                     ;
  462.                 } else {
  463.                     $event = new CoreWorkflowEvents\Ticket\AgentReply();
  464.                     $event
  465.                         ->setTicket($ticket)
  466.                     ;
  467.                 }
  468.             }
  469.             // Trigger thread reply event
  470.             $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  471.         } else if (false === $ticket->getIsTrashed() && strtolower($ticket->getStatus()->getCode()) != 'spam' && empty($mailData['inReplyTo'])) {
  472.             return [
  473.                 'message' => "The contents of this email has already been processed."
  474.                 'content' => [
  475.                     'from' => !empty($mailData['from']) ? $mailData['from'] : null
  476.                     'thread' => !empty($thread) ? $thread->getId() : null
  477.                     'ticket' => !empty($ticket) ? $ticket->getId() : null
  478.                 ], 
  479.             ];
  480.         }
  481.         return [
  482.             'message' => "Inbound email processsed successfully."
  483.             'content' => [
  484.                 'from' => !empty($mailData['from']) ? $mailData['from'] : null
  485.                 'thread' => !empty($thread) ? $thread->getId() : null
  486.                 'ticket' => !empty($ticket) ? $ticket->getId() : null
  487.             ], 
  488.         ];
  489.     }
  490. }