|
| 1 | +# Email Templates |
| 2 | + |
| 3 | +[Back to the list of all defined endpoints](endpoints.md) |
| 4 | + |
| 5 | +This endpoint exposes DSpace email templates to administrators, allowing them to view and edit email templates stored in `{dspace.dir}/config/emails/`. |
| 6 | + |
| 7 | +Only administrators are allowed to access and edit these templates. |
| 8 | +Templates cannot be created or deleted via the REST API because they correspond to hardcoded templates in the DSpace backend. |
| 9 | + |
| 10 | +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. |
| 11 | + |
| 12 | +## Optimistic Concurrency Control |
| 13 | + |
| 14 | +To prevent concurrent administrators from overwriting each other's changes, this endpoint implements optimistic concurrency control via HTTP conditional headers: |
| 15 | + |
| 16 | +* **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..."`). |
| 17 | +* **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**. |
| 18 | +* **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. |
| 19 | + * If `If-Match` is missing, the server responds with **428 Precondition Required**. |
| 20 | + * If none of the provided ETags match the current template version, the server responds with **412 Precondition Failed**. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Retrieve all email templates |
| 25 | + |
| 26 | +GET `/api/system/emailtemplates` |
| 27 | + |
| 28 | +Returns a list of all email templates, ordered alphabetically by name. |
| 29 | + |
| 30 | +### Status Codes for Retrieve All |
| 31 | + |
| 32 | +* 200 OK - if the operation succeeded |
| 33 | +* 401 Unauthorized - if not logged in |
| 34 | +* 403 Forbidden - if logged in as a non-admin user |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +## Retrieve a specific email template |
| 39 | + |
| 40 | +GET `/api/system/emailtemplates/<:template-name>` |
| 41 | + |
| 42 | +Returns a single email template by its filename identifier (e.g., `register`, `feedback`). |
| 43 | + |
| 44 | +### Example Response |
| 45 | + |
| 46 | +```json |
| 47 | +{ |
| 48 | + "name": "register", |
| 49 | + "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", |
| 50 | + "subject": "${config.get('dspace.name')} Account Registration", |
| 51 | + "mimetype": "text/plain", |
| 52 | + "variables": [ |
| 53 | + { |
| 54 | + "index": 0, |
| 55 | + "description": "is expanded to a special registration URL", |
| 56 | + "placeholder": "${params[0]}" |
| 57 | + } |
| 58 | + ], |
| 59 | + "lastModified": "2026-09-05T12:00:00Z", |
| 60 | + "etag": "da56428b0c502de5a058cdd47cdf5fc446d90fc68ed2cf096d3bf6ae53ba568a", |
| 61 | + "type": "emailtemplate", |
| 62 | + "_links": { |
| 63 | + "self": { |
| 64 | + "href": "http://localhost:8080/server/api/system/emailtemplates/register" |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +### Template Properties |
| 71 | + |
| 72 | +* **name**: Unique template identifier matching filename. Pattern `^[a-zA-Z0-9._-]+$`, max 255 chars. |
| 73 | +* **subject**: Extracted subject line from `#set($subject = ...)` directive, max 500 chars. |
| 74 | +* **mimetype**: Enum indicating the content type. Possible values: |
| 75 | + * `"text/plain"`: Standard plain text email (default). |
| 76 | + * `"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)). |
| 77 | +* **content**: Raw VTL template body, 10 to 50,000 chars. |
| 78 | +* **variables**: Array of parsed parameter variables (`index`, `description` max 500 chars, `placeholder` max 30 chars e.g. `${params[0]}`). |
| 79 | +* **lastModified**: ISO-8601 timestamp of when the file was last modified on disk. |
| 80 | +* **etag**: Deterministic SHA-256 hash of the template content for concurrency control. |
| 81 | + |
| 82 | +### Status Codes for Retrieve Specific |
| 83 | + |
| 84 | +* 200 OK - if the operation succeeded |
| 85 | +* 304 Not Modified - if any candidate in `If-None-Match` matches current ETag (or `*`) |
| 86 | +* 401 Unauthorized - if not logged in |
| 87 | +* 403 Forbidden - if logged in as a non-admin user |
| 88 | +* 404 Not Found - if no template exists with the specified name |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## Update an email template |
| 93 | + |
| 94 | +PUT `/api/system/emailtemplates/<:template-name>` |
| 95 | + |
| 96 | +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). |
| 97 | + |
| 98 | +### Required Request Headers |
| 99 | + |
| 100 | +* `Content-Type: application/json` |
| 101 | +* `If-Match: "<current-etag>"` (also accepts a comma-separated list of ETags, `W/"<current-etag>"`, or `*`) |
| 102 | + |
| 103 | +### Example Request Body |
| 104 | + |
| 105 | +```json |
| 106 | +{ |
| 107 | + "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" |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +### Status Codes for Update |
| 112 | + |
| 113 | +* 200 OK - if the template was successfully updated (returns updated template and new `ETag` header) |
| 114 | +* 400 Bad Request - if the content fails validation (invalid VTL syntax, XSS script tags, or length < 10 or > 50,000 chars) |
| 115 | +* 401 Unauthorized - if not logged in |
| 116 | +* 403 Forbidden - if logged in as a non-admin user |
| 117 | +* 404 Not Found - if no template exists with the specified name |
| 118 | +* 412 Precondition Failed - if none of the candidate ETags in `If-Match` match the current template version |
| 119 | +* 428 Precondition Required - if the `If-Match` header was omitted |
0 commit comments