Skip to content

Commit da24958

Browse files
feat: use task processing to send emails
Signed-off-by: SebastianKrupinski <[email protected]>
1 parent 1c24b8c commit da24958

File tree

4 files changed

+263
-2
lines changed

4 files changed

+263
-2
lines changed

lib/AppInfo/Application.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
use OCA\Mail\Service\UserPreferenceService;
6969
use OCA\Mail\SetupChecks\MailConnectionPerformance;
7070
use OCA\Mail\SetupChecks\MailTransport;
71+
use OCA\Mail\TaskProcessing\MailSendTask;
72+
use OCA\Mail\TaskProcessing\TaskProcessingProvider;
7173
use OCA\Mail\Vendor\Favicon\Favicon;
7274
use OCP\AppFramework\App;
7375
use OCP\AppFramework\Bootstrap\IBootContext;
@@ -164,6 +166,9 @@ public function register(IRegistrationContext $context): void {
164166
$context->registerSetupCheck(MailTransport::class);
165167
$context->registerSetupCheck(MailConnectionPerformance::class);
166168

169+
$context->registerTaskProcessingProvider(TaskProcessingProvider::class);
170+
$context->registerTaskProcessingTaskType(MailSendTask::class);
171+
167172
// bypass Horde Translation system
168173
Horde_Translation::setHandler('Horde_Imap_Client', new HordeTranslationHandler());
169174
Horde_Translation::setHandler('Horde_Mime', new HordeTranslationHandler());

lib/Provider/MailService.php

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
*/
99
namespace OCA\Mail\Provider;
1010

11+
use OCA\Mail\AppInfo\Application;
1112
use OCA\Mail\Provider\Command\MessageSend;
13+
use OCA\Mail\TaskProcessing\MailSendTask;
1214
use OCP\Mail\Provider\Address;
1315
use OCP\Mail\Provider\Exception\SendException;
1416
use OCP\Mail\Provider\IAddress;
1517
use OCP\Mail\Provider\IMessage;
1618
use OCP\Mail\Provider\IMessageSend;
1719
use OCP\Mail\Provider\IService;
1820
use OCP\Mail\Provider\Message;
19-
21+
use OCP\TaskProcessing\IManager as TaskProcessingManager;
22+
use OCP\TaskProcessing\Task;
2023
use Psr\Container\ContainerInterface;
2124

2225
class MailService implements IService, IMessageSend {
@@ -178,12 +181,49 @@ public function initiateMessage(): IMessage {
178181
* @since 4.0.0
179182
*
180183
* @param IMessage $message mail message object with all required parameters to send a message
181-
* @param array $options array of options reserved for future use
184+
* @param array{sendMode?:'instant'|'queued'|'deferred',sendDelay?:int} $options array of options reserved for future use
185+
*
186+
* $options['sendMode'] can be:
187+
* - 'instant': sends the message immediately (connects to the mail service right away)
188+
* - 'queued': adds the message to the task processing queue to be sent as soon as possible
189+
* - 'deferred': adds the message to the task processing queue to be sent after a delay
190+
* $options['sendDelay'] is only applicable if 'sendMode' is 'deferred' and specifies the delay in seconds before sending the message
182191
*
183192
* @throws SendException on failure, check message for reason
184193
*/
185194
#[\Override]
186195
public function sendMessage(IMessage $message, array $options = []): void {
196+
// if send mode is not set, default to queued
197+
if (!isset($options['sendMode'])) {
198+
$options['sendMode'] = 'queued';
199+
}
200+
// if send mode is not instant use task processing this sends the message as soon as possible
201+
if ($options['sendMode'] !== 'instant') {
202+
$taskProcessingManager = $this->container->get(TaskProcessingManager::class);
203+
$availableTaskTypes = $taskProcessingManager->getAvailableTaskTypes();
204+
// if task processing is available use it
205+
if (isset($availableTaskTypes[MailSendTask::ID])) {
206+
$task = new Task(
207+
MailSendTask::ID,
208+
[
209+
'userId' => $this->userId,
210+
'serviceId' => $this->serviceId,
211+
'message' => $message,
212+
'options' => $options,
213+
],
214+
Application::APP_ID,
215+
null
216+
);
217+
218+
if ($options['sendMode'] === 'deferred' && isset($options['sendDelay']) && is_int($options['sendDelay'])) {
219+
$task->setScheduledAt(time() + $options['sendDelay']);
220+
}
221+
222+
$taskProcessingManager->scheduleTask($task);
223+
return;
224+
}
225+
}
226+
// fallback to instant send
187227
/** @var MessageSend $cmd */
188228
$cmd = $this->container->get(MessageSend::class);
189229
// perform action
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Mail\TaskProcessing;
11+
12+
use OCA\Mail\Provider\Command\MessageSend;
13+
use OCP\IL10N;
14+
use OCP\Mail\Provider\Message;
15+
use OCP\TaskProcessing\EShapeType;
16+
use OCP\TaskProcessing\ISynchronousProvider;
17+
use OCP\TaskProcessing\ShapeDescriptor;
18+
use Psr\Container\ContainerInterface;
19+
20+
/**
21+
* This is the task processing provider for sending messages via the mail app.
22+
*
23+
* @since 5.6.0
24+
*/
25+
class MailSendProvider implements ISynchronousProvider {
26+
27+
public function __construct(
28+
private readonly ContainerInterface $container,
29+
private readonly IL10N $l,
30+
) {
31+
}
32+
33+
public function getId(): string {
34+
return 'mail:send';
35+
}
36+
37+
public function getName(): string {
38+
return 'Mail Send Provider';
39+
}
40+
41+
public function getTaskTypeId(): string {
42+
return 'mail:send';
43+
}
44+
45+
public function getExpectedRuntime(): int {
46+
return 60;
47+
}
48+
49+
public function getOptionalInputShape(): array {
50+
return [
51+
'userId' => new ShapeDescriptor(
52+
$this->l->t('User ID'),
53+
$this->l->t('The ID of the user sending the email'),
54+
EShapeType::Text
55+
),
56+
'serviceId' => new ShapeDescriptor(
57+
$this->l->t('Service ID'),
58+
$this->l->t('The ID of the service/account sending the email'),
59+
EShapeType::Number
60+
),
61+
'message' => new ShapeDescriptor(
62+
$this->l->t('Message'),
63+
$this->l->t('The email message to be sent (OCP\Mail\IMessage)'),
64+
EShapeType::Object
65+
),
66+
'options' => new ShapeDescriptor(
67+
$this->l->t('Options'),
68+
$this->l->t('Additional options for sending the email'),
69+
EShapeType::Array
70+
),
71+
];
72+
73+
}
74+
75+
public function getOptionalOutputShape(): array {
76+
return [];
77+
}
78+
79+
public function getInputShapeEnumValues(): array {
80+
return [];
81+
}
82+
83+
public function getInputShapeDefaults(): array {
84+
return [];
85+
}
86+
87+
public function getOptionalInputShapeEnumValues(): array {
88+
return [];
89+
}
90+
91+
public function getOptionalInputShapeDefaults(): array {
92+
return [];
93+
}
94+
95+
public function getOutputShapeEnumValues(): array {
96+
return [];
97+
}
98+
99+
public function getOptionalOutputShapeEnumValues(): array {
100+
return [];
101+
}
102+
103+
public function process(?string $userId, array $input, callable $reportProgress): array {
104+
// extract parameters
105+
$userId = $input['userId'] ?? null;
106+
$serviceId = $input['serviceId'] ?? null;
107+
$options = $input['options'] ?? [];
108+
if (isset($input['message'])) {
109+
$message = new Message();
110+
$message->jsonDeserialize((array)$input['message']);
111+
} else {
112+
$message = null;
113+
}
114+
// validate parameters
115+
if ($userId === null || empty($userId)) {
116+
throw new \InvalidArgumentException('Invalid or missing userId');
117+
}
118+
if ($serviceId === null || empty($serviceId) || $serviceId <= 0) {
119+
throw new \InvalidArgumentException('Invalid or missing serviceId');
120+
}
121+
if (!$message instanceof Message) {
122+
throw new \InvalidArgumentException('Invalid or missing message');
123+
}
124+
// perform task
125+
/** @var MessageSend $cmd */
126+
$cmd = $this->container->get(MessageSend::class);
127+
$cmd->perform($userId, (string)$serviceId, $message, $options);
128+
129+
return [];
130+
}
131+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Mail\TaskProcessing;
11+
12+
use OCP\IL10N;
13+
use OCP\L10N\IFactory;
14+
use OCP\TaskProcessing\EShapeType;
15+
use OCP\TaskProcessing\ITaskType;
16+
use OCP\TaskProcessing\ShapeDescriptor;
17+
18+
/**
19+
* This is the task processing task type for sending messages via the mail app.
20+
*
21+
* @since 5.6.0
22+
*/
23+
class MailSendTask implements ITaskType {
24+
25+
public const ID = 'mail:send';
26+
private IL10N $l;
27+
28+
public function __construct(
29+
private readonly IFactory $l10nFactory,
30+
) {
31+
$this->l = $l10nFactory->get('lib');
32+
}
33+
34+
public function getName(): string {
35+
return $this->l->t('Mail Send');
36+
}
37+
38+
public function getDescription(): string {
39+
return $this->l->t('Send an email using the mail app.');
40+
}
41+
42+
public function getId(): string {
43+
return self::ID;
44+
}
45+
46+
public function getInputShape(): array {
47+
return [
48+
'userId' => new ShapeDescriptor(
49+
$this->l->t('User ID'),
50+
$this->l->t('The ID of the user sending the email'),
51+
EShapeType::Text
52+
),
53+
'serviceId' => new ShapeDescriptor(
54+
$this->l->t('Service ID'),
55+
$this->l->t('The ID of the service/account sending the email'),
56+
EShapeType::Number
57+
),
58+
'message' => new ShapeDescriptor(
59+
$this->l->t('Message'),
60+
$this->l->t('The email message to be sent (OCP\Mail\IMessage)'),
61+
EShapeType::Object
62+
),
63+
'options' => new ShapeDescriptor(
64+
$this->l->t('Options'),
65+
$this->l->t('Additional options for sending the email'),
66+
EShapeType::Array
67+
),
68+
];
69+
}
70+
71+
public function getOutputShape(): array {
72+
return [
73+
'status_code' => new ShapeDescriptor(
74+
$this->l->t('Status Code'),
75+
$this->l->t('The status code of the email sending operation'),
76+
EShapeType::Number
77+
),
78+
'status_message' => new ShapeDescriptor(
79+
$this->l->t('Status Message'),
80+
$this->l->t('A message describing the result of the email sending operation'),
81+
EShapeType::Text
82+
),
83+
];
84+
}
85+
}

0 commit comments

Comments
 (0)