Skip to content
Merged
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
7 changes: 7 additions & 0 deletions admin_manual/configuration_server/email_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ settings and look the same in all the different email clients out there.
Modifying the look of emails beyond the theming app capabilities
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

.. warning:: Nextcloud 36 redesigned the email template: every email is now a single card,
with dark mode and right-to-left support. A custom class overriding the HTML fragments of the
previous design keeps working, but its parts will look out of place next to the new ones.
Review it against the new ``EMailTemplate`` before upgrading. Calendar emails also no longer
pass the ``meeting_*_html`` values to the template, they pass ``meeting_*_previous`` for
changed values instead.

You can overwrite templates by writing a class that implements the template interface
(or extends it to not need to copy over everything). Easiest way is to then put this class into
an app and load it so you do not need to patch it on every update.
Expand Down
112 changes: 112 additions & 0 deletions developer_manual/digging_deeper/email.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,118 @@ The mailer is hidden behind the ``\OCP\Mail\IMailer`` interface that can be :ref
}
}

Email templates
---------------

Most emails should not build their own HTML. ``IMailer::createEMailTemplate()`` returns an
``\OCP\Mail\IEMailTemplate`` that renders a themed HTML email and a matching plain text version,
with dark mode support for the mail clients that have it. Attach it to a message with
``IMessage::useTemplate()``:

.. code-block:: php

$template = $this->mailer->createEMailTemplate('myapp.ProjectShared', [
'project' => $projectName,
]);
$template->setLanguage($l->getLanguageCode());
$template->setSubject($l->t('%s shared a project with you', [$sharerName]));
$template->addHeader();
$template->addHeading($l->t('%s shared a project with you', [$sharerName]));
$template->addBodyText($l->t('You can now see and edit the project.'));
$template->addBodyButton($l->t('Open project'), $projectUrl);
$template->addFooter();

$message = $this->mailer->createMessage();
$message->setTo([$recipientEmail]);
$message->useTemplate($template);
$this->mailer->send($message);

The first argument of ``createEMailTemplate()`` identifies the email (``<app>.<EmailName>``), and
the second one passes data to custom templates (see `Modifying the look of emails
<https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/email_configuration.html>`_
in the administration manual).

Write the email in the recipient's language: get an ``IL10N`` for their language from
``\OCP\L10N\IFactory`` and pass its code to ``setLanguage()``. The template then sets the right
``lang`` and ``dir`` attributes, mirrors the layout for right-to-left languages like Arabic or
Hebrew, and writes the default footer in that language. Call it before ``addFooter()``.

Building blocks
^^^^^^^^^^^^^^^

Since Nextcloud 36, the template has blocks to show who an email is about, a highlighted note, a
card with details, and a row of buttons. Unlike ``addBodyText()`` with a separate plain text, these
blocks never take HTML: every value is escaped by the template, URLs included, and each block
writes its own plain text version.

.. figure:: ../images/email-template-blocks.png
:alt: An email with a sender, a heading, a details card with a calendar badge, a description note, a warning note and three buttons
:width: 500px

A sender, a details card, two notes and a row of buttons.

``addBodySender()``
The person the email is sent for (sharer, organizer...), with an initials circle, their name
and an optional second line such as their email address. Add it before the heading.

``addBodyNote()``
A highlighted box. Use ``IEMailTemplate::NOTE_NEUTRAL`` (the default) for text written by a
user, like a share note or an event description, with the label shown above it. Use
``NOTE_INFO``, ``NOTE_WARNING`` or ``NOTE_ERROR`` for messages from the server, with the label
as a bold title. Line breaks are kept.

``addBodyDetails()``
A card describing an item: title, subtitle, an initials circle or a calendar badge, and
labelled rows. See below.

``addBodyButtons()``
Any number of buttons, the first one is the primary action, with an optional question above
them like "Will you attend?".

The details card is built with ``\OCP\Mail\EMailDetails``. Each row holds one or more parts, each
rendered on its own line: plain text, a link, or muted secondary text.

.. code-block:: php

use OCP\Mail\EMailDetails;
use OCP\Mail\IEMailTemplate;

$template->addHeader();
$template->addBodySender($organizerName, $organizerEmail);
$template->addHeading($l->t('%s invited you to an event', [$organizerName]));

$details = (new EMailDetails($eventTitle))
->setSubtitle($l->l('date', $start, ['width' => 'full']))
->setDateBadge($l->l('date', $start, ['width' => '~MMM']), $l->l('date', $start, ['width' => '~d']));
$details->addRow($l->t('Where'))
->text($location)
->link($l->t('Join in Nextcloud Talk'), $talkUrl);
$details->addRow($l->t('Attendees'))
->text($firstAttendee)
->muted($l->n('and %n other', 'and %n others', $remaining));
$template->addBodyDetails($details);

$template->addBodyNote($description, $l->t('Description'));
$template->addBodyButtons([
['text' => $l->t('Accept'), 'url' => $acceptUrl],
['text' => $l->t('Decline'), 'url' => $declineUrl],
], $l->t('Will you attend?'));
$template->addFooter();

Use ``setInitials($name)`` instead of ``setDateBadge()`` to show an initials circle, for example
for a conversation or a team. The badge strings are passed as is, so localize them yourself.

A short note about a shared item goes before its details card. Longer content, like an event
description, goes after it.

Footer
^^^^^^

``addFooter()`` without a text adds the default footer: the instance name and slogan, then
"This is an automatically sent email, please do not reply.". When the email has a sender block,
the second line becomes "This email was sent from <instance> on behalf of <name>." instead.
Pass a text to ``addFooter()`` to replace the default footer entirely.

Inline attachments
------------------

Expand Down
Binary file added developer_manual/images/email-template-blocks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions developer_manual/release_notes/new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ This pages covers new features of the platform.
Every feature should just have a brief description. Details have to be documented on a dedicated, persistent page.
After branch-off the contents below will be cleared.

Email template blocks
---------------------

The email template (``\OCP\Mail\IEMailTemplate``) was redesigned with dark mode and
right-to-left support, and has new building blocks so emails no longer need hand-built HTML:

- ``addBodySender()`` shows who the email is about, with an initials circle.
- ``addBodyNote()`` shows a highlighted note from a user, or an info, warning or error message.
- ``addBodyDetails()`` shows a card with a title and labelled rows, built with ``\OCP\Mail\EMailDetails``.
- ``addBodyButtons()`` shows any number of buttons.
- ``setLanguage()`` sets the language of the email, which also mirrors the layout for right-to-left languages.

All values are escaped by the template. See :ref:`email` for details.

New ``\OCP\Files\IUserFolder`` API
----------------------------------

Expand Down
Loading