@@ -37,6 +37,118 @@ The mailer is hidden behind the ``\OCP\Mail\IMailer`` interface that can be :ref
3737 }
3838 }
3939
40+ Email templates
41+ ---------------
42+
43+ Most emails should not build their own HTML. ``IMailer::createEMailTemplate() `` returns an
44+ ``\OCP\Mail\IEMailTemplate `` that renders a themed HTML email and a matching plain text version,
45+ with dark mode support for the mail clients that have it. Attach it to a message with
46+ ``IMessage::useTemplate() ``:
47+
48+ .. code-block :: php
49+
50+ $template = $this->mailer->createEMailTemplate('myapp.ProjectShared', [
51+ 'project' => $projectName,
52+ ]);
53+ $template->setLanguage($l->getLanguageCode());
54+ $template->setSubject($l->t('%s shared a project with you', [$sharerName]));
55+ $template->addHeader();
56+ $template->addHeading($l->t('%s shared a project with you', [$sharerName]));
57+ $template->addBodyText($l->t('You can now see and edit the project.'));
58+ $template->addBodyButton($l->t('Open project'), $projectUrl);
59+ $template->addFooter();
60+
61+ $message = $this->mailer->createMessage();
62+ $message->setTo([$recipientEmail]);
63+ $message->useTemplate($template);
64+ $this->mailer->send($message);
65+
66+ The first argument of ``createEMailTemplate() `` identifies the email (``<app>.<EmailName> ``), and
67+ the second one passes data to custom templates (see `Modifying the look of emails
68+ <https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/email_configuration.html> `_
69+ in the administration manual).
70+
71+ Write the email in the recipient's language: get an ``IL10N `` for their language from
72+ ``\OCP\L10N\IFactory `` and pass its code to ``setLanguage() ``. The template then sets the right
73+ ``lang `` and ``dir `` attributes, mirrors the layout for right-to-left languages like Arabic or
74+ Hebrew, and writes the default footer in that language. Call it before ``addFooter() ``.
75+
76+ Building blocks
77+ ^^^^^^^^^^^^^^^
78+
79+ Since Nextcloud 36, the template has blocks to show who an email is about, a highlighted note, a
80+ card with details, and a row of buttons. Unlike ``addBodyText() `` with a separate plain text, these
81+ blocks never take HTML: every value is escaped by the template, URLs included, and each block
82+ writes its own plain text version.
83+
84+ .. figure :: ../images/email-template-blocks.png
85+ :alt: An email with a sender, a heading, a details card with a calendar badge, a description note, a warning note and three buttons
86+ :width: 500px
87+
88+ A sender, a details card, two notes and a row of buttons.
89+
90+ ``addBodySender() ``
91+ The person the email is sent for (sharer, organizer...), with an initials circle, their name
92+ and an optional second line such as their email address. Add it before the heading.
93+
94+ ``addBodyNote() ``
95+ A highlighted box. Use ``IEMailTemplate::NOTE_NEUTRAL `` (the default) for text written by a
96+ user, like a share note or an event description, with the label shown above it. Use
97+ ``NOTE_INFO ``, ``NOTE_WARNING `` or ``NOTE_ERROR `` for messages from the server, with the label
98+ as a bold title. Line breaks are kept.
99+
100+ ``addBodyDetails() ``
101+ A card describing an item: title, subtitle, an initials circle or a calendar badge, and
102+ labelled rows. See below.
103+
104+ ``addBodyButtons() ``
105+ Any number of buttons, the first one is the primary action, with an optional question above
106+ them like "Will you attend?".
107+
108+ The details card is built with ``\OCP\Mail\EMailDetails ``. Each row holds one or more parts, each
109+ rendered on its own line: plain text, a link, or muted secondary text.
110+
111+ .. code-block :: php
112+
113+ use OCP\Mail\EMailDetails;
114+ use OCP\Mail\IEMailTemplate;
115+
116+ $template->addHeader();
117+ $template->addBodySender($organizerName, $organizerEmail);
118+ $template->addHeading($l->t('%s invited you to an event', [$organizerName]));
119+
120+ $details = (new EMailDetails($eventTitle))
121+ ->setSubtitle($l->l('date', $start, ['width' => 'full']))
122+ ->setDateBadge($l->l('date', $start, ['width' => '~MMM']), $l->l('date', $start, ['width' => '~d']));
123+ $details->addRow($l->t('Where'))
124+ ->text($location)
125+ ->link($l->t('Join in Nextcloud Talk'), $talkUrl);
126+ $details->addRow($l->t('Attendees'))
127+ ->text($firstAttendee)
128+ ->muted($l->n('and %n other', 'and %n others', $remaining));
129+ $template->addBodyDetails($details);
130+
131+ $template->addBodyNote($description, $l->t('Description'));
132+ $template->addBodyButtons([
133+ ['text' => $l->t('Accept'), 'url' => $acceptUrl],
134+ ['text' => $l->t('Decline'), 'url' => $declineUrl],
135+ ], $l->t('Will you attend?'));
136+ $template->addFooter();
137+
138+ Use ``setInitials($name) `` instead of ``setDateBadge() `` to show an initials circle, for example
139+ for a conversation or a team. The badge strings are passed as is, so localize them yourself.
140+
141+ A short note about a shared item goes before its details card. Longer content, like an event
142+ description, goes after it.
143+
144+ Footer
145+ ^^^^^^
146+
147+ ``addFooter() `` without a text adds the default footer: the instance name and slogan, then
148+ "This is an automatically sent email, please do not reply.". When the email has a sender block,
149+ the second line becomes "This email was sent from <instance> on behalf of <name>." instead.
150+ Pass a text to ``addFooter() `` to replace the default footer entirely.
151+
40152Inline attachments
41153------------------
42154
0 commit comments