Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1520e22
feat: jmap support - part 2
SebastianKrupinski Apr 21, 2026
ff8de5a
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 4, 2026
6781b28
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 4, 2026
56ac80f
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 4, 2026
de5294f
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 6, 2026
2084981
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 10, 2026
e408d95
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 19, 2026
16e09e8
fixup! feat: jmap support - part 2
kesselb Aug 19, 2026
8f6ef83
fixup! feat: jmap support - part 2
kesselb Aug 19, 2026
8221942
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 19, 2026
542fafb
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 19, 2026
70c260a
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 26, 2026
1c822dc
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 26, 2026
231a6ab
fixup! feat: jmap support - part 2
SebastianKrupinski Aug 31, 2026
6ab5baf
refactor(imap): revert getMessagesByMessageId -> getByMessageId rename
ChristophWurst Sep 1, 2026
e89fad8
refactor(imap): revert getSource -> getRawMessage rename
ChristophWurst Sep 1, 2026
e8e81b3
Merge pull request #13612 from ChristophWurst/chore/jmap-2-revert-get…
ChristophWurst Sep 2, 2026
e9d0450
Merge pull request #13610 from ChristophWurst/chore/jmap-2-revert-get…
ChristophWurst Sep 2, 2026
1026793
fix(imap): pass through $loadBody in ImapMessageConnector::fetchMessages
ChristophWurst Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,15 @@ jobs:
image: stalwartlabs/stalwart:v0.15.5
env:
STALWART_ADMIN_PASSWORD: ${{ env.STALWART_PWD }}
# Pin a stable container hostname. Stalwart derives the authority of
# its published JMAP discovery URLs (apiUrl/downloadUrl/uploadUrl) from
# the container hostname + internal port, which is otherwise a random
# container id the runner cannot resolve. With a known hostname we can
# map it to loopback via /etc/hosts and publish the internal port 1:1
# so the advertised http://stalwart:8080/jmap/ is reachable.
options: --hostname stalwart
ports:
- 10080:8080
- 8080:8080
- 10025:25
- 10143:143
- 10993:993
Expand Down Expand Up @@ -213,13 +220,15 @@ jobs:
ports:
- 6379:6379
steps:
- name: Map Stalwart hostname to loopback
run: echo "127.0.0.1 stalwart" | sudo tee -a /etc/hosts
- name: Create domain and account in Stalwart
run: |
curl -sf -X POST http://localhost:10080/api/principal \
curl -sf -X POST http://stalwart:8080/api/principal \
-u "admin:${{ env.STALWART_PWD }}" \
-H 'Content-Type: application/json' \
-d '{"type":"domain","name":"example.com"}'
curl -sf -X POST http://localhost:10080/api/principal \
curl -sf -X POST http://stalwart:8080/api/principal \
-u "admin:${{ env.STALWART_PWD }}" \
-H 'Content-Type: application/json' \
-d '{"type":"individual","name":"user@example.com","secrets":["mypassword"],"emails":["user@example.com"],"roles":["user"]}'
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"phpmailer/dkimvalidator": "^0.3.1",
"rubix/ml": "2.5.5",
"sabberworm/php-css-parser": "^9.4.0",
"sebastiankrupinski/jmap-client-php": "^2.0.0",
"sebastiankrupinski/jmap-client-php": "^2.1.0",
"wamania/php-stemmer": "4.0 as 3.0",
"youthweb/urllinker": "^2.1.0"
},
Expand Down
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use OCA\Mail\Contracts\IAvatarService;
use OCA\Mail\Contracts\IDkimService;
use OCA\Mail\Contracts\IDkimValidator;
use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\Contracts\IMailTransmission;
use OCA\Mail\Contracts\ITrustedSenderService;
Expand Down Expand Up @@ -63,7 +62,6 @@
use OCA\Mail\Service\AvatarService;
use OCA\Mail\Service\DkimService;
use OCA\Mail\Service\DkimValidator;
use OCA\Mail\Service\MailManager;
use OCA\Mail\Service\MailTransmission;
use OCA\Mail\Service\Search\MailSearch;
use OCA\Mail\Service\TrustedSenderService;
Expand Down Expand Up @@ -121,7 +119,6 @@ public function register(IRegistrationContext $context): void {

$context->registerServiceAlias(IAvatarService::class, AvatarService::class);
$context->registerServiceAlias(IAttachmentService::class, AttachmentService::class);
$context->registerServiceAlias(IMailManager::class, MailManager::class);
$context->registerServiceAlias(IMailSearch::class, MailSearch::class);
$context->registerServiceAlias(IMailTransmission::class, MailTransmission::class);
$context->registerServiceAlias(ITrustedSenderService::class, TrustedSenderService::class);
Expand Down
69 changes: 28 additions & 41 deletions lib/BackgroundJob/ContextChat/SubmitContentJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use OCA\Mail\Db\MessageMapper;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\SmimeDecryptException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\ContextChat\TaskService;
use OCA\Mail\Service\MailManager;
Expand All @@ -36,7 +35,6 @@ public function __construct(
private AccountService $accountService,
private MailManager $mailManager,
private MessageMapper $messageMapper,
private IMAPClientFactory $clientFactory,
private ContextChatProvider $contextChatProvider,
private IContentManager $contentManager,
private LoggerInterface $logger,
Expand Down Expand Up @@ -108,50 +106,39 @@ protected function run($argument): void {
return;
}

$client = $this->clientFactory->getClient($account);
$items = [];

try {
$startTime = $this->time->getTime();
foreach ($messages as $message) {
if ($this->time->getTime() - $startTime > ContextChatProvider::CONTEXT_CHAT_JOB_INTERVAL) {
break;
}
try {
$imapMessage = $this->mailManager->getImapMessage($client, $account, $mailbox, $message->getUid(), true);
} catch (ServiceException $e) {
// couldn't load message, let's skip it. Retrying would be too costly
continue;
} catch (SmimeDecryptException $e) {
// encryption problem, skip this message
continue;
}

// Skip encrypted messages
if ($imapMessage->isEncrypted()) {
continue;
}

$fullMessage = $imapMessage->getFullMessage($imapMessage->getUid(), true);

$items[] = new ContentItem(
"{$mailbox->getId()}:{$message->getId()}",
$this->contextChatProvider->getId(),
$imapMessage->getSubject(),
$fullMessage['body'] ?? '',
'E-Mail',
$imapMessage->getSentDate(),
[$account->getUserId()],
);
$startTime = $this->time->getTime();
foreach ($messages as $message) {
if ($this->time->getTime() - $startTime > ContextChatProvider::CONTEXT_CHAT_JOB_INTERVAL) {
break;
}
} catch (\Throwable $e) {
$this->logger->warning('Exception occurred when trying to fetch messages for context chat', ['exception' => $e]);
} finally {
try {
$client->close();
} catch (\Horde_Imap_Client_Exception $e) {
$this->logger->debug('Failed to close IMAP client', ['exception' => $e]);
$imapMessage = $this->mailManager->getImapMessage($account, $mailbox, $message, true);
} catch (ServiceException $e) {
// couldn't load message, let's skip it. Retrying would be too costly
continue;
} catch (SmimeDecryptException $e) {
// encryption problem, skip this message
continue;
}

// Skip encrypted messages
if ($imapMessage->isEncrypted()) {
continue;
}

$fullMessage = $imapMessage->getFullMessage($imapMessage->getUid(), true);

$items[] = new ContentItem(
"{$mailbox->getId()}:{$message->getId()}",
$this->contextChatProvider->getId(),
$imapMessage->getSubject(),
$fullMessage['body'] ?? '',
'E-Mail',
$imapMessage->getSentDate(),
[$account->getUserId()],
);
}

if (count($items) > 0) {
Expand Down
10 changes: 5 additions & 5 deletions lib/BackgroundJob/FollowUpClassifierJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

namespace OCA\Mail\BackgroundJob;

use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\ThreadMapper;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
use OCA\Mail\Service\MailManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
use OCP\DB\Exception;
Expand All @@ -31,7 +31,7 @@ public function __construct(
ITimeFactory $time,
private LoggerInterface $logger,
private AccountService $accountService,
private IMailManager $mailManager,
private MailManager $mailManager,
private AiIntegrationsService $aiService,
private ThreadMapper $threadMapper,
) {
Expand Down Expand Up @@ -104,12 +104,12 @@ public function run($argument): void {

$this->logger->debug("Message requires follow-up: {$message->getId()}");
$tag = $this->mailManager->createTag('Follow up', '#d77000', $userId);
$this->mailManager->tagMessage(
$this->mailManager->tagMessages(
$account,
$mailbox->getName(),
$message,
$mailbox,
$tag,
true,
$message,
);
}
}
8 changes: 4 additions & 4 deletions lib/BackgroundJob/MigrateImportantJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Migration\MigrateImportantFromImapAndDb;
use OCA\Mail\Protocol\ProtocolFactory;
use OCA\Mail\Service\MailManager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
Expand All @@ -29,7 +29,7 @@ public function __construct(
private MigrateImportantFromImapAndDb $migration,
private LoggerInterface $logger,
ITimeFactory $timeFactory,
private IMAPClientFactory $imapClientFactory,
private ProtocolFactory $protocolFactory,
) {
parent::__construct($timeFactory);
}
Expand Down Expand Up @@ -58,10 +58,10 @@ public function run($argument) {
}

$account = new Account($mailAccount);
$client = $this->imapClientFactory->getClient($account);
$client = $this->protocolFactory->imapClient($account);

try {
if ($this->mailManager->isPermflagsEnabled($client, $account, $mailbox->getName()) === false) {
if ($this->mailManager->isPermflagsEnabled($account, $mailbox) === false) {
$this->logger->debug("Permflags not enabled for <{$accountId}>");
return;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/BackgroundJob/QuotaJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

namespace OCA\Mail\BackgroundJob;

use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\MailManager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
Expand All @@ -28,7 +28,7 @@ public function __construct(
ITimeFactory $time,
IUserManager $userManager,
private AccountService $accountService,
private IMailManager $mailManager,
private MailManager $mailManager,
IManager $notificationManager,
private LoggerInterface $logger,
IJobList $jobList,
Expand Down
16 changes: 16 additions & 0 deletions lib/BackgroundJob/RepairSyncJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

namespace OCA\Mail\BackgroundJob;

use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Events\SynchronizationEvent;
use OCA\Mail\Protocol\ProtocolFactory;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\Sync\SyncService;
use OCP\AppFramework\Db\DoesNotExistException;
Expand All @@ -25,6 +27,7 @@ class RepairSyncJob extends TimedJob {
public function __construct(
ITimeFactory $time,
private SyncService $syncService,
private ProtocolFactory $protocolFactory,
private AccountService $accountService,
private IUserManager $userManager,
private MailboxMapper $mailboxMapper,
Expand Down Expand Up @@ -55,6 +58,15 @@ protected function run($argument): void {
return;
}

if ($account->getMailAccount()->getProtocol() !== MailAccount::PROTOCOL_IMAP) {
$this->logger->debug(sprintf(
'Account %d uses %s, skipping IMAP repair sync after mailbox refresh',
$account->getId(),
$account->getMailAccount()->getProtocol(),
));
return;
}

$user = $this->userManager->get($account->getUserId());
if ($user === null || !$user->isEnabled()) {
$this->logger->debug(sprintf(
Expand All @@ -65,6 +77,10 @@ protected function run($argument): void {
return;
}

$this->protocolFactory
->mailboxConnector($account)
->syncAll($account, true);

$rebuildThreads = false;
$trashMailboxId = $account->getMailAccount()->getTrashMailboxId();
$snoozeMailboxId = $account->getMailAccount()->getSnoozeMailboxId();
Expand Down
18 changes: 11 additions & 7 deletions lib/BackgroundJob/SyncJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

use Horde_Imap_Client_Exception;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Exception\IncompleteSyncException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\Protocol\ProtocolFactory;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\Sync\ImapToDbSynchronizer;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
Expand All @@ -38,8 +38,7 @@ public function __construct(
ITimeFactory $time,
IUserManager $userManager,
private AccountService $accountService,
private MailboxSync $mailboxSync,
private ImapToDbSynchronizer $syncService,
private ProtocolFactory $protocolFactory,
private LoggerInterface $logger,
IJobList $jobList,
private readonly IConfig $config,
Expand Down Expand Up @@ -87,7 +86,8 @@ protected function run($argument) {
return;
}

if (!$account->getMailAccount()->canAuthenticateImap()) {
if ($account->getMailAccount()->getProtocol() === MailAccount::PROTOCOL_IMAP
&& !$account->getMailAccount()->canAuthenticateImap()) {
$this->logger->debug('No authentication on IMAP possible, skipping background sync job');
return;
}
Expand Down Expand Up @@ -128,8 +128,12 @@ protected function run($argument) {
}

try {
$this->mailboxSync->sync($account, $this->logger, true);
$this->syncService->syncAccount($account, $this->logger);
$this->protocolFactory
->mailboxConnector($account)
->syncAll($account, true);
$this->protocolFactory
->messageConnector($account)
->syncAll($account, false);
} catch (IncompleteSyncException $e) {
$this->logger->warning($e->getMessage(), [
'exception' => $e,
Expand Down
Loading
Loading