diff --git a/authentik/events/models.py b/authentik/events/models.py
index 965e38cc2..240ba11de 100644
--- a/authentik/events/models.py
+++ b/authentik/events/models.py
@@ -461,7 +461,7 @@ class NotificationTransport(SerializerModel):
}
mail = TemplateEmailMessage(
subject=subject_prefix + context["title"],
- to=[notification.user.email],
+ to=[f"{notification.user.name} <{notification.user.email}>"],
language=notification.user.locale(),
template_name="email/event_notification.html",
template_context=context,
diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py
index 0fa36bfbe..160a68e92 100644
--- a/authentik/stages/email/stage.py
+++ b/authentik/stages/email/stage.py
@@ -110,7 +110,7 @@ class EmailStageView(ChallengeStageView):
try:
message = TemplateEmailMessage(
subject=_(current_stage.subject),
- to=[email],
+ to=[f"{pending_user.name} <{email}>"],
language=pending_user.locale(self.request),
template_name=current_stage.template,
template_context={
diff --git a/authentik/stages/email/templates/email/account_confirmation.txt b/authentik/stages/email/templates/email/account_confirmation.txt
new file mode 100644
index 000000000..0a1fc70d1
--- /dev/null
+++ b/authentik/stages/email/templates/email/account_confirmation.txt
@@ -0,0 +1,8 @@
+{% load i18n %}{% translate "Welcome!" %}
+
+{% translate "We're excited to have you get started. First, you need to confirm your account. Just open the link below." %}
+
+{{ url }}
+
+--
+Powered by goauthentik.io.
diff --git a/authentik/stages/email/templates/email/event_notification.html b/authentik/stages/email/templates/email/event_notification.html
index e34563404..7ca78fc64 100644
--- a/authentik/stages/email/templates/email/event_notification.html
+++ b/authentik/stages/email/templates/email/event_notification.html
@@ -44,7 +44,7 @@
{% blocktranslate with name=source.from %}
- This email was sent from the notification transport {{name}} .
+ This email was sent from the notification transport {{ name }} .
{% endblocktranslate %}
|
diff --git a/authentik/stages/email/templates/email/event_notification.txt b/authentik/stages/email/templates/email/event_notification.txt
new file mode 100644
index 000000000..bd7d92896
--- /dev/null
+++ b/authentik/stages/email/templates/email/event_notification.txt
@@ -0,0 +1,18 @@
+{% load authentik_stages_email %}{% load i18n %}{% translate "Dear authentik user," %}
+
+{% translate "The following notification was created:" %}
+
+ {{ body|indent }}
+
+{% if key_value %}
+{% translate "Additional attributes:" %}
+{% for key, value in key_value.items %}
+ {{ key }}: {{ value|indent }}{% endfor %}
+{% endif %}
+
+{% if source %}{% blocktranslate with name=source.from %}
+This email was sent from the notification transport {{ name }}.
+{% endblocktranslate %}{% endif %}
+
+--
+Powered by goauthentik.io.
diff --git a/authentik/stages/email/templates/email/password_reset.txt b/authentik/stages/email/templates/email/password_reset.txt
new file mode 100644
index 000000000..0c13ad2f8
--- /dev/null
+++ b/authentik/stages/email/templates/email/password_reset.txt
@@ -0,0 +1,12 @@
+{% load i18n %}{% load humanize %}{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}
+
+{% blocktrans %}
+You recently requested to change your password for your authentik account. Use the link below to set a new password.
+{% endblocktrans %}
+{{ url }}
+{% blocktrans with expires=expires|naturaltime %}
+If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
+{% endblocktrans %}
+
+--
+Powered by goauthentik.io.
diff --git a/authentik/stages/email/templates/email/setup.txt b/authentik/stages/email/templates/email/setup.txt
new file mode 100644
index 000000000..6d0eb0ce0
--- /dev/null
+++ b/authentik/stages/email/templates/email/setup.txt
@@ -0,0 +1,7 @@
+{% load i18n %}authentik Test-Email
+{% blocktrans %}
+This is a test email to inform you, that you've successfully configured authentik emails.
+{% endblocktrans %}
+
+--
+Powered by goauthentik.io.
diff --git a/authentik/stages/email/templatetags/authentik_stages_email.py b/authentik/stages/email/templatetags/authentik_stages_email.py
index 7623c6c71..9b3dfb194 100644
--- a/authentik/stages/email/templatetags/authentik_stages_email.py
+++ b/authentik/stages/email/templatetags/authentik_stages_email.py
@@ -29,3 +29,9 @@ def inline_static_binary(path: str) -> str:
b64content = b64encode(_file.read().encode())
return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}"
return path
+
+
+@register.filter(name="indent")
+def indent_string(val, num_spaces=4):
+ """Intent text by a given amount of spaces"""
+ return val.replace("\n", "\n" + " " * num_spaces)
diff --git a/authentik/stages/email/tests/test_sending.py b/authentik/stages/email/tests/test_sending.py
index 424d474ce..5c67c8842 100644
--- a/authentik/stages/email/tests/test_sending.py
+++ b/authentik/stages/email/tests/test_sending.py
@@ -58,9 +58,11 @@ class TestEmailStageSending(FlowTestCase):
events = Event.objects.filter(action=EventAction.EMAIL_SENT)
self.assertEqual(len(events), 1)
event = events.first()
- self.assertEqual(event.context["message"], f"Email to {self.user.email} sent")
+ self.assertEqual(
+ event.context["message"], f"Email to {self.user.name} <{self.user.email}> sent"
+ )
self.assertEqual(event.context["subject"], "authentik")
- self.assertEqual(event.context["to_email"], [self.user.email])
+ self.assertEqual(event.context["to_email"], [f"{self.user.name} <{self.user.email}>"])
self.assertEqual(event.context["from_email"], "system@authentik.local")
def test_pending_fake_user(self):
diff --git a/authentik/stages/email/tests/test_stage.py b/authentik/stages/email/tests/test_stage.py
index 853bc2a77..277549937 100644
--- a/authentik/stages/email/tests/test_stage.py
+++ b/authentik/stages/email/tests/test_stage.py
@@ -94,7 +94,7 @@ class TestEmailStage(FlowTestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
- self.assertEqual(mail.outbox[0].to, [self.user.email])
+ self.assertEqual(mail.outbox[0].to, [f"{self.user.name} <{self.user.email}>"])
@patch(
"authentik.stages.email.models.EmailStage.backend_class",
@@ -114,7 +114,7 @@ class TestEmailStage(FlowTestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
- self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"])
+ self.assertEqual(mail.outbox[0].to, [f"{self.user.name} "])
@patch(
"authentik.stages.email.models.EmailStage.backend_class",
diff --git a/authentik/stages/email/utils.py b/authentik/stages/email/utils.py
index a6edd4609..8f7f702ce 100644
--- a/authentik/stages/email/utils.py
+++ b/authentik/stages/email/utils.py
@@ -4,6 +4,7 @@ from functools import lru_cache
from pathlib import Path
from django.core.mail import EmailMultiAlternatives
+from django.template.exceptions import TemplateDoesNotExist
from django.template.loader import render_to_string
from django.utils import translation
@@ -24,9 +25,15 @@ class TemplateEmailMessage(EmailMultiAlternatives):
"""Wrapper around EmailMultiAlternatives with integrated template rendering"""
def __init__(self, template_name=None, template_context=None, language="", **kwargs):
+ super().__init__(**kwargs)
with translation.override(language):
html_content = render_to_string(template_name, template_context)
- super().__init__(**kwargs)
- self.content_subtype = "html"
+ try:
+ text_content = render_to_string(
+ template_name.replace("html", "txt"), template_context
+ )
+ self.body = text_content
+ except TemplateDoesNotExist:
+ pass
self.mixed_subtype = "related"
self.attach_alternative(html_content, "text/html")
diff --git a/web/src/components/ak-event-info.ts b/web/src/components/ak-event-info.ts
index 6901f31a8..e728958c2 100644
--- a/web/src/components/ak-event-info.ts
+++ b/web/src/components/ak-event-info.ts
@@ -285,10 +285,12 @@ export class EventInfo extends AKElement {
}
renderEmailSent() {
+ let body = this.event.context.body as string;
+ body = body.replace("cid:logo.png", "/static/dist/assets/icons/icon_left_brand.png");
return html`${msg("Email info:")}
${this.getEmailInfo(this.event.context)}
-
+
`;
}
diff --git a/website/docs/flow/stages/email/email_recovery.png b/website/docs/flow/stages/email/email_recovery.png
index 1dc5dbbc4..0bc14bafd 100644
Binary files a/website/docs/flow/stages/email/email_recovery.png and b/website/docs/flow/stages/email/email_recovery.png differ
diff --git a/website/docs/flow/stages/email/index.mdx b/website/docs/flow/stages/email/index.mdx
index 1064d8f13..a7751e295 100644
--- a/website/docs/flow/stages/email/index.mdx
+++ b/website/docs/flow/stages/email/index.mdx
@@ -25,6 +25,10 @@ return True
You can also use custom email templates, to use your own design or layout.
+:::info
+Starting with authentik 2024.1, it is possible to create `.txt` files with the same name as the `.html` template. If a matching `.txt` file exists, the email sent will be a multipart email with both the text and HTML template.
+:::
+
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
@@ -81,13 +85,17 @@ Templates are rendered using Django's templating engine. The following variables
- `user`: The pending user object.
- `expires`: The timestamp when the token expires.
+
+
```html
-{# This is how you can write comments which aren't rendered. #} {# Extend this
-template from the base email template, which includes base layout and CSS. #} {%
-extends "email/base.html" %} {# Load the internationalization module to
-translate strings, and humanize to show date-time #} {% load i18n %} {% load
-humanize %} {# The email/base.html template uses a single "content" block #} {%
-block content %}
+{# This is how you can write comments which aren't rendered. #}
+{# Extend this template from the base email template, which includes base layout and CSS. #}
+{% extends "email/base.html" %}
+{# Load the internationalization module to translate strings, and humanize to show date-time #}
+{% load i18n %}
+{% load humanize %}
+{# The email/base.html template uses a single "content" block #}
+{% block content %}
{% blocktrans with username=user.username %} Hi {{ username }}, {%
@@ -99,9 +107,9 @@ block content %}
- {% blocktrans %} You recently requested to change your
- password for you authentik account. Use the button below to
- set a new password. {% endblocktrans %}
+ {% blocktrans %}
+ You recently requested to change your password for you authentik account. Use the button below to set a new password.
+ {% endblocktrans %}
|
@@ -130,8 +138,7 @@ block content %}
href="{{ url }}"
rel="noopener noreferrer"
target="_blank"
- >{% trans 'Reset
- Password' %}{% trans 'Reset Password' %}
@@ -145,9 +152,9 @@ block content %}
- {% blocktrans with expires=expires|naturaltime %} If you did
- not request a password change, please ignore this Email. The
- link above is valid for {{ expires }}. {% endblocktrans %}
+ {% blocktrans with expires=expires|naturaltime %}
+ If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
+ {% endblocktrans %}
|
@@ -155,3 +162,5 @@ block content %}
|
{% endblock %}
```
+
+