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
119 changes: 119 additions & 0 deletions emailtemplates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Email Templates

[Back to the list of all defined endpoints](endpoints.md)

This endpoint exposes DSpace email templates to administrators, allowing them to view and edit email templates stored in `{dspace.dir}/config/emails/`.

Only administrators are allowed to access and edit these templates.
Templates cannot be created or deleted via the REST API because they correspond to hardcoded templates in the DSpace backend.

System configuration variables usable inside templates (e.g. `${config.get('dspace.name')}`) are deliberately **not** embedded in template payloads. They are exposed via the dedicated [/api/system/systemconfigvariables](systemconfigvariables.md) endpoint.

## Optimistic Concurrency Control

To prevent concurrent administrators from overwriting each other's changes, this endpoint implements optimistic concurrency control via HTTP conditional headers:

* **ETag Header**: Every `GET` and successful `PUT` response includes an `ETag` response header containing a SHA-256 hash of the template content (e.g., `ETag: "da56428b0c..."`).
* **If-None-Match**: When retrieving a template with `GET`, clients may provide the `If-None-Match` header containing an entity tag, a comma-separated list of entity tags, weak validators (e.g. `W/"..."`), or `*`. If any candidate matches the current template, the server responds with **304 Not Modified**.
* **If-Match Required on PUT**: Every `PUT` request **must** provide the `If-Match` request header. It accepts an entity tag, a comma-separated list of entity tags, weak validators (e.g. `W/"..."`), or `*` for unconditional overwrite.
* If `If-Match` is missing, the server responds with **428 Precondition Required**.
* If none of the provided ETags match the current template version, the server responds with **412 Precondition Failed**.

---

## Retrieve all email templates

GET `/api/system/emailtemplates`

Returns a list of all email templates, ordered alphabetically by name.

### Status Codes for Retrieve All

* 200 OK - if the operation succeeded
* 401 Unauthorized - if not logged in
* 403 Forbidden - if logged in as a non-admin user

---

## Retrieve a specific email template

GET `/api/system/emailtemplates/<:template-name>`

Returns a single email template by its filename identifier (e.g., `register`, `feedback`).

### Example Response

```json
{
"name": "register",
"content": "## E-mail sent to DSpace users when they register for an account\n##\n## Parameters: {0} is expanded to a special registration URL\n##\n#set($subject = \"${config.get('dspace.name')} Account Registration\")\nTo complete registration, click: ${params[0]}\n",
"subject": "${config.get('dspace.name')} Account Registration",
"mimetype": "text/plain",
"variables": [
{
"index": 0,
"description": "is expanded to a special registration URL",
"placeholder": "${params[0]}"
}
],
"lastModified": "2026-09-05T12:00:00Z",
"etag": "da56428b0c502de5a058cdd47cdf5fc446d90fc68ed2cf096d3bf6ae53ba568a",
"type": "emailtemplate",
"_links": {
"self": {
"href": "http://localhost:8080/server/api/system/emailtemplates/register"
}
}
}
```

### Template Properties

* **name**: Unique template identifier matching filename. Pattern `^[a-zA-Z0-9._-]+$`, max 255 chars.
* **subject**: Extracted subject line from `#set($subject = ...)` directive, max 500 chars.
* **mimetype**: Enum indicating the content type. Possible values:
* `"text/plain"`: Standard plain text email (default).
* `"text/html"`: HTML-formatted email, detected when the template specifies `#set($mimetype = "text/html")` (supported via [DSpace PR #11755](https://github.com/DSpace/DSpace/pull/11755)).
* **content**: Raw VTL template body, 10 to 50,000 chars.
* **variables**: Array of parsed parameter variables (`index`, `description` max 500 chars, `placeholder` max 30 chars e.g. `${params[0]}`).
* **lastModified**: ISO-8601 timestamp of when the file was last modified on disk.
* **etag**: Deterministic SHA-256 hash of the template content for concurrency control.

### Status Codes for Retrieve Specific

* 200 OK - if the operation succeeded
* 304 Not Modified - if any candidate in `If-None-Match` matches current ETag (or `*`)
* 401 Unauthorized - if not logged in
* 403 Forbidden - if logged in as a non-admin user
* 404 Not Found - if no template exists with the specified name

---

## Update an email template

PUT `/api/system/emailtemplates/<:template-name>`

Replaces the full content of the email template. The submitted content is preserved as-is (retaining leading indentation and blank lines) while ensuring a trailing newline. The template content is validated against Velocity (VTL) syntax rules, XSS sanitization (OWASP), and size constraints (10 to 50,000 characters).

### Required Request Headers

* `Content-Type: application/json`
* `If-Match: "<current-etag>"` (also accepts a comma-separated list of ETags, `W/"<current-etag>"`, or `*`)

### Example Request Body

```json
{
"content": "## E-mail sent to DSpace users when they register for an account\n##\n## Parameters: {0} is expanded to a special registration URL\n##\n#set($subject = \"${config.get('dspace.name')} Account Registration\")\nTo complete registration, please visit: ${params[0]}\n"
}
```

### Status Codes for Update

* 200 OK - if the template was successfully updated (returns updated template and new `ETag` header)
* 400 Bad Request - if the content fails validation (invalid VTL syntax, XSS script tags, or length < 10 or > 50,000 chars)
* 401 Unauthorized - if not logged in
* 403 Forbidden - if logged in as a non-admin user
* 404 Not Found - if no template exists with the specified name
* 412 Precondition Failed - if none of the candidate ETags in `If-Match` match the current template version
* 428 Precondition Required - if the `If-Match` header was omitted
2 changes: 2 additions & 0 deletions endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
* [/api/submission/vocabularyEntryDetails](vocabularyEntryDetails.md)
* [/api/system/auditevents](auditevents.md)
* [/api/system/systemwidealerts](systemwidealerts.md)
* [/api/system/emailtemplates](emailtemplates.md)
* [/api/system/systemconfigvariables](systemconfigvariables.md)
* [/api/versioning/versions](versions.md)
* [/api/versioning/versionhistories](versionhistories.md)
* [/api/workflow/workflowitems](workflowitems.md)
Expand Down
104 changes: 104 additions & 0 deletions systemconfigvariables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# System Config Variables

[Back to the list of all defined endpoints](endpoints.md)

This endpoint exposes the general repository configuration variables that administrators may reference inside email templates (e.g. `${config.get('dspace.name')}`).

It returns the subset of configuration properties allowed in templates, as defined by repository configuration (e.g. `message.templates.allowed-config`).

Only administrators are allowed to access these variables. They are exposed here — decoupled from the [/api/system/emailtemplates](emailtemplates.md) payloads — so clients fetch the identical list once instead of receiving it duplicated inside every template.

---

## Retrieve all system config variables

GET `/api/system/systemconfigvariables`

Returns a list of all allowed system configuration variables, ordered alphabetically by key.

### Example Response (paginated, HAL-embedded)

```json
{
"_embedded": {
"systemconfigvariables": [
{
"key": "dspace.name",
"value": "DSpace at My University",
"placeholder": "${config.get('dspace.name')}",
"type": "systemconfigvariable",
"_links": {
"self": {
"href": "http://localhost:8080/server/api/system/systemconfigvariables/dspace.name"
}
}
},
{
"key": "dspace.ui.url",
"value": "http://localhost:4000",
"placeholder": "${config.get('dspace.ui.url')}",
"type": "systemconfigvariable",
"_links": {
"self": {
"href": "http://localhost:8080/server/api/system/systemconfigvariables/dspace.ui.url"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/server/api/system/systemconfigvariables"
}
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
```

### Variable Properties

* **key**: Configuration property key (e.g. `dspace.name`). Pattern `^[a-zA-Z0-9._-]+$`, max 255 chars.
* **value**: Resolved value of the configuration property, max 2000 chars.
* **placeholder**: Velocity expression to insert into a template (e.g. `${config.get('dspace.name')}`), max 255 chars.

### Status Codes for Retrieve All

* 200 OK - if the operation succeeded
* 401 Unauthorized - if not logged in
* 403 Forbidden - if logged in as a non-admin user

---

## Retrieve a specific system config variable

GET `/api/system/systemconfigvariables/<:property-key>`

Returns a single configuration variable by its property key (e.g., `dspace.name`).

### Example Response

```json
{
"key": "dspace.name",
"value": "DSpace at My University",
"placeholder": "${config.get('dspace.name')}",
"type": "systemconfigvariable",
"_links": {
"self": {
"href": "http://localhost:8080/server/api/system/systemconfigvariables/dspace.name"
}
}
}
```

### Status Codes for Retrieve Specific

* 200 OK - if the operation succeeded
* 401 Unauthorized - if not logged in
* 403 Forbidden - if logged in as a non-admin user
* 404 Not Found - if no allowed variable exists with the specified key
Loading