Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions CHANGELOG.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@

# Changelog

## Unreleased

- **Confirmation emails for respondents**

Form owners can enable an automatic confirmation email that is sent to the respondent after a successful submission.
Requires an email-validated short text question in the form.

Supported placeholders in subject/body:

- `{formTitle}`, `{formDescription}`
- `{<fieldName>}` (question `name` or text, sanitized)

## v5.2.0 - 2025-09-25

- **Time: restrictions and ranges**
Expand Down
4 changes: 4 additions & 0 deletions docs/API_v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ Returns the full-depth object of the requested form (without submissions).
"state": 0,
"lockedBy": null,
"lockedUntil": null,
"confirmationEmailEnabled": false,
"confirmationEmailSubject": null,
"confirmationEmailBody": null,
"confirmationEmailQuestionId": null,
"permissions": [
"edit",
"results",
Expand Down
58 changes: 33 additions & 25 deletions docs/DataStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,35 @@ This document describes the Object-Structure, that is used within the Forms App

### Form

| Property | Type | Restrictions | Description |
| -------------------- | ------------------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| id | Integer | unique | An instance-wide unique id of the form |
| hash | 16-char String | unique | An instance-wide unique hash |
| title | String | max. 256 ch. | The form title |
| description | String | max. 8192 ch. | The Form description |
| ownerId | String | | The nextcloud userId of the form owner |
| submissionMessage | String | max. 2048 ch. | Optional custom message, with Markdown support, to be shown to users when the form is submitted (default is used if set to null) |
| created | unix timestamp | | When the form has been created |
| access | [Access-Object](#access-object) | | Describing access-settings of the form |
| expires | unix-timestamp | | When the form should expire. Timestamp `0` indicates _never_ |
| isAnonymous | Boolean | | If Answers will be stored anonymously |
| state | Integer | [Form state](#form-state) | The state of the form |
| lockedBy | String | | The user ID for who has exclusive edit access at the moment |
| lockedUntil | unix timestamp | | When the form lock will expire |
| submitMultiple | Boolean | | If users are allowed to submit multiple times to the form |
| allowEditSubmissions | Boolean | | If users are allowed to edit or delete their response |
| showExpiration | Boolean | | If the expiration date will be shown on the form |
| canSubmit | Boolean | | If the user can Submit to the form, i.e. calculated information out of `submitMultiple` and existing submissions. |
| permissions | Array of [Permissions](#permissions) | Array of permissions regarding the form |
| questions | Array of [Questions](#question) | | Array of questions belonging to the form |
| shares | Array of [Shares](#share) | | Array of shares of the form |
| submissions | Array of [Submissions](#submission) | | Array of submissions belonging to the form |
| submissionCount | Integer | | Number of submissions to the form |
| submissionMessage | String | | Message to show after a submission |
| Property | Type | Restrictions | Description |
| --------------------------- | ------------------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| id | Integer | unique | An instance-wide unique id of the form |
| hash | 16-char String | unique | An instance-wide unique hash |
| title | String | max. 256 ch. | The form title |
| description | String | max. 8192 ch. | The Form description |
| ownerId | String | | The nextcloud userId of the form owner |
| submissionMessage | String | max. 2048 ch. | Optional custom message, with Markdown support, to be shown to users when the form is submitted (default is used if set to null) |
| confirmationEmailEnabled | Boolean | | If enabled, send a confirmation email to the respondent after submission |
| confirmationEmailSubject | String | max. 255 ch. | Optional confirmation email subject template (supports placeholders) |
| confirmationEmailBody | String | | Optional confirmation email body template (plain text, supports placeholders) |
| confirmationEmailQuestionId | Integer | | The id of the question belonging to the form, that holds the email address of the respondent |
| created | unix timestamp | | When the form has been created |
| access | [Access-Object](#access-object) | | Describing access-settings of the form |
| expires | unix-timestamp | | When the form should expire. Timestamp `0` indicates _never_ |
| isAnonymous | Boolean | | If Answers will be stored anonymously |
| state | Integer | [Form state](#form-state) | The state of the form |
| lockedBy | String | | The user ID for who has exclusive edit access at the moment |
| lockedUntil | unix timestamp | | When the form lock will expire |
| submitMultiple | Boolean | | If users are allowed to submit multiple times to the form |
| allowEditSubmissions | Boolean | | If users are allowed to edit or delete their response |
| showExpiration | Boolean | | If the expiration date will be shown on the form |
| canSubmit | Boolean | | If the user can Submit to the form, i.e. calculated information out of `submitMultiple` and existing submissions. |
| permissions | Array of [Permissions](#permissions) | Array of permissions regarding the form |
| questions | Array of [Questions](#question) | | Array of questions belonging to the form |
| shares | Array of [Shares](#share) | | Array of shares of the form |
| submissions | Array of [Submissions](#submission) | | Array of submissions belonging to the form |
| submissionCount | Integer | | Number of submissions to the form |
| submissionMessage | String | | Message to show after a submission |

```
{
Expand All @@ -46,6 +50,10 @@ This document describes the Object-Structure, that is used within the Forms App
"title": "Form 1",
"description": "Description Text",
"ownerId": "jonas",
"confirmationEmailEnabled": false,
"confirmationEmailSubject": null,
"confirmationEmailBody": null,
"confirmationEmailQuestionId": null,
"created": 1611240961,
"access": {},
"expires": 0,
Expand Down
63 changes: 63 additions & 0 deletions lib/BackgroundJob/SendConfirmationMailJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Forms\BackgroundJob;

use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
use OCP\Defaults;
use OCP\Mail\IMailer;
use OCP\Util;
use Psr\Log\LoggerInterface;

class SendConfirmationMailJob extends QueuedJob {
public function __construct(
ITimeFactory $time,
private IMailer $mailer,
private LoggerInterface $logger,
private Defaults $defaults,
) {
parent::__construct($time);
}

/**
* @param array{recipient: string, subject: string, body: string, formId: int, submissionId: int} $argument
*/
public function run($argument): void {
$recipient = $argument['recipient'];
$subject = $argument['subject'];
$body = $argument['body'];
$formId = $argument['formId'];
$submissionId = $argument['submissionId'];

try {
$emailTemplate = $this->mailer->createEMailTemplate('forms.Confirmation');
$emailTemplate->setSubject($subject);
$emailTemplate->addHeader();
$emailTemplate->addHeading($subject);
$emailTemplate->addBodyText($body);
$emailTemplate->addFooter();

$message = $this->mailer->createMessage();
$message->setFrom([Util::getDefaultEmailAddress('noreply') => $this->defaults->getName()]);
$message->setTo([$recipient]);
$message->useTemplate($emailTemplate);
$this->mailer->send($message);
$this->logger->debug('Confirmation email sent successfully', [
'formId' => $formId,
'submissionId' => $submissionId,
]);
} catch (\Exception $e) {
$this->logger->error('Error while sending confirmation email', [
'exception' => $e,
'formId' => $formId,
'submissionId' => $submissionId,
]);
}
}
}
8 changes: 7 additions & 1 deletion lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ class Constants {
public const CONFIG_KEY_ALLOWSHOWTOALL = 'allowShowToAll';
public const CONFIG_KEY_CREATIONALLOWEDGROUPS = 'creationAllowedGroups';
public const CONFIG_KEY_RESTRICTCREATION = 'restrictCreation';
public const CONFIG_KEY_ALLOWCONFIRMATIONEMAIL = 'allowConfirmationEmail';
public const CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT = 'confirmationEmailRateLimit';
public const CONFIG_KEYS = [
self::CONFIG_KEY_ALLOWPERMITALL,
self::CONFIG_KEY_ALLOWPUBLICLINK,
self::CONFIG_KEY_ALLOWSHOWTOALL,
self::CONFIG_KEY_CREATIONALLOWEDGROUPS,
self::CONFIG_KEY_RESTRICTCREATION
self::CONFIG_KEY_RESTRICTCREATION,
self::CONFIG_KEY_ALLOWCONFIRMATIONEMAIL,
self::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT,
];

/**
Expand All @@ -33,6 +37,8 @@ class Constants {
'formTitle' => 256,
'formDescription' => 8192,
'submissionMessage' => 2048,
'confirmationEmailSubject' => 255,
'confirmationEmailBody' => 8192,
'questionText' => 2048,
'questionDescription' => 4096,
'optionText' => 1024,
Expand Down
Loading