diff --git a/src/openforms/registrations/contrib/email/plugin.py b/src/openforms/registrations/contrib/email/plugin.py index 1e0e7b6e69..92679273ab 100644 --- a/src/openforms/registrations/contrib/email/plugin.py +++ b/src/openforms/registrations/contrib/email/plugin.py @@ -3,6 +3,8 @@ from typing import Any from django.conf import settings +from django.core.exceptions import ValidationError +from django.core.validators import validate_email from django.urls import reverse from django.utils import timezone from django.utils.translation import get_language_info, gettext_lazy as _ @@ -42,11 +44,41 @@ class EmailRegistration(BasePlugin[Options]): verbose_name = _("Email registration") configuration_options = EmailOptionsSerializer + def get_recipients(self, submission: Submission, options: Options) -> list[str]: + state = submission.load_submission_value_variables_state() + recipients = [] + + # If the 'recipients from variable' is used and it exists + if ( + (variable_key := options.get("to_emails_from_variable")) + and variable_key in state.variables + and (variable_value := state.variables[variable_key].value) + ): + # To simplify things, treat all variable values as lists + if type(variable_value) != list: + variable_value = [variable_value] + + # Only if all email addresses are valid, they will be used as recipients + try: + for value in variable_value: + validate_email(value) + except ValidationError: + pass + else: + recipients = variable_value + + # If the variable cannot be used, fallback to the general email addresses + if not recipients and "to_emails" in options: + recipients = options["to_emails"] + + return recipients + def register_submission(self, submission: Submission, options: Options) -> None: config = EmailConfig.get_solo() config.apply_defaults_to(options) - self.send_registration_email(options["to_emails"], submission, options) + recipients = self.get_recipients(submission, options) + self.send_registration_email(recipients, submission, options) # ensure that the payment email is also sent if registration is deferred until # payment is completed @@ -187,8 +219,9 @@ def send_registration_email( def update_payment_status(self, submission: "Submission", options: Options): recipients = options.get("payment_emails") + if not recipients: - recipients = options["to_emails"] + recipients = self.get_recipients(submission, options) order_ids = submission.payments.get_completed_public_order_ids() extra_context = { diff --git a/src/openforms/registrations/contrib/email/tests/test_backend.py b/src/openforms/registrations/contrib/email/tests/test_backend.py index 3fd74b4a4a..ceaee381c6 100644 --- a/src/openforms/registrations/contrib/email/tests/test_backend.py +++ b/src/openforms/registrations/contrib/email/tests/test_backend.py @@ -249,6 +249,158 @@ def test_submission_with_email_backend(self): self.assertIn(f"{expected_download_url_1} (my-foo.bin)", message_text) self.assertIn(f"{expected_download_url_2} (my-bar.txt)", message_text) + def test_submission_with_email_backend_using_to_emails_from_variable(self): + submission = SubmissionFactory.from_components( + completed=True, + components_list=[ + {"key": "foo", "type": "textfield", "label": "foo"}, + ], + submitted_data={"foo": "bar"}, + form__registration_backend="email", + ) + SubmissionValueVariableFactory.create( + form_variable__source=FormVariableSources.user_defined, + form_variable__name="User defined var 1", + submission=submission, + key="email_recipient_variable", + value="foo@example.com", + ) + email_form_options = dict( + to_emails_from_variable="email_recipient_variable", + ) + email_submission = EmailRegistration("email") + + set_submission_reference(submission) + + with patch("openforms.registrations.contrib.email.utils.EmailConfig.get_solo"): + email_submission.register_submission(submission, email_form_options) + + # Verify that email was sent + self.assertEqual(len(mail.outbox), 1) + + message = mail.outbox[0] + self.assertEqual(message.to, ["foo@example.com"]) + + def test_submission_with_email_backend_using_to_emails_from_variable_with_multiple_email_addresses( + self, + ): + submission = SubmissionFactory.from_components( + completed=True, + components_list=[ + {"key": "foo", "type": "textfield", "label": "foo"}, + ], + submitted_data={"foo": "bar"}, + form__registration_backend="email", + ) + SubmissionValueVariableFactory.create( + form_variable__source=FormVariableSources.user_defined, + form_variable__name="User defined var 1", + submission=submission, + key="email_recipient_variable", + value=["foo@example.com", "bar@example.com"], + ) + email_form_options = dict( + to_emails_from_variable="email_recipient_variable", + ) + email_submission = EmailRegistration("email") + + set_submission_reference(submission) + + with patch("openforms.registrations.contrib.email.utils.EmailConfig.get_solo"): + email_submission.register_submission(submission, email_form_options) + + # Verify that email was sent + self.assertEqual(len(mail.outbox), 1) + + message = mail.outbox[0] + self.assertEqual(message.to, ["foo@example.com", "bar@example.com"]) + + def test_submission_with_email_backend_unknown_to_emails_from_variable(self): + submission = SubmissionFactory.from_components( + completed=True, + components_list=[ + {"key": "foo", "type": "textfield", "label": "foo"}, + ], + submitted_data={"foo": "bar"}, + form__registration_backend="email", + ) + email_form_options = dict( + to_emails_from_variable="email_recipient_variable", + ) + email_submission = EmailRegistration("email") + + set_submission_reference(submission) + + with patch("openforms.registrations.contrib.email.utils.EmailConfig.get_solo"): + email_submission.register_submission(submission, email_form_options) + + # Verify that email wasn't sent + self.assertEqual(len(mail.outbox), 0) + + def test_submission_with_email_backend_invalid_to_emails_from_variable(self): + submission = SubmissionFactory.from_components( + completed=True, + components_list=[ + {"key": "foo", "type": "textfield", "label": "foo"}, + ], + submitted_data={"foo": "bar"}, + form__registration_backend="email", + ) + SubmissionValueVariableFactory.create( + form_variable__source=FormVariableSources.user_defined, + form_variable__name="User defined var 1", + submission=submission, + key="email_recipient_variable", + value="foo.com", + ) + email_form_options = dict( + to_emails_from_variable="email_recipient_variable", + ) + email_submission = EmailRegistration("email") + + set_submission_reference(submission) + + with patch("openforms.registrations.contrib.email.utils.EmailConfig.get_solo"): + email_submission.register_submission(submission, email_form_options) + + # Verify that email wasn't sent + self.assertEqual(len(mail.outbox), 0) + + def test_submission_with_email_backend_invalid_to_emails_from_variable_with_fallback( + self, + ): + submission = SubmissionFactory.from_components( + completed=True, + components_list=[ + {"key": "foo", "type": "textfield", "label": "foo"}, + ], + submitted_data={"foo": "bar"}, + form__registration_backend="email", + ) + SubmissionValueVariableFactory.create( + form_variable__source=FormVariableSources.user_defined, + form_variable__name="User defined var 1", + submission=submission, + key="email_recipient_variable", + value="foo.com", + ) + email_form_options = dict( + to_emails_from_variable="email_recipient_variable", + to_emails=["bar@example.com"], + ) + email_submission = EmailRegistration("email") + + set_submission_reference(submission) + + with patch("openforms.registrations.contrib.email.utils.EmailConfig.get_solo"): + email_submission.register_submission(submission, email_form_options) + + # Verify that email was sent + self.assertEqual(len(mail.outbox), 1) + + message = mail.outbox[0] + self.assertEqual(message.to, ["bar@example.com"]) + def test_submission_with_email_backend_strip_out_urls(self): config = GlobalConfiguration.get_solo() config.email_template_netloc_allowlist = [] @@ -477,6 +629,71 @@ def test_register_and_update_paid_product_with_payment_email_recipient(self): # check we used the payment_emails self.assertEqual(message.to, ["payment@bar.nl", "payment@foo.nl"]) + def test_register_and_update_paid_product_with_payment_email_recipient_and_variable_email_recipient( + self, + ): + submission = SubmissionFactory.from_data( + {"voornaam": "Foo"}, + form__product__price=Decimal("11.35"), + form__payment_backend="demo", + registration_success=True, + public_registration_reference="XYZ", + ) + SubmissionValueVariableFactory.create( + form_variable__source=FormVariableSources.user_defined, + form_variable__name="User defined var 1", + submission=submission, + key="email_recipient_variable", + value="foo@example.com", + ) + + email_form_options = dict( + to_emails=["foo@bar.nl", "bar@foo.nl"], + to_emails_from_variable="email_recipient_variable", + # payment_emails would override to_emails and to_emails_from_variable + payment_emails=["payment@bar.nl", "payment@foo.nl"], + ) + email_submission = EmailRegistration("email") + email_submission.update_payment_status(submission, email_form_options) + + self.assertEqual(len(mail.outbox), 1) + + message = mail.outbox[0] + # check we used the payment_emails + self.assertEqual(message.to, ["payment@bar.nl", "payment@foo.nl"]) + + def test_register_and_update_paid_product_with_variable_email_recipient( + self, + ): + submission = SubmissionFactory.from_data( + {"voornaam": "Foo"}, + form__product__price=Decimal("11.35"), + form__payment_backend="demo", + registration_success=True, + public_registration_reference="XYZ", + ) + SubmissionValueVariableFactory.create( + form_variable__source=FormVariableSources.user_defined, + form_variable__name="User defined var 1", + submission=submission, + key="email_recipient_variable", + value="foo@example.com", + ) + + email_form_options = dict( + to_emails=["foo@bar.nl", "bar@foo.nl"], + # to_emails_from_variable would override to_emails + to_emails_from_variable="email_recipient_variable", + ) + email_submission = EmailRegistration("email") + email_submission.update_payment_status(submission, email_form_options) + + self.assertEqual(len(mail.outbox), 1) + + message = mail.outbox[0] + # check we used the payment_emails + self.assertEqual(message.to, ["foo@example.com"]) + @override_settings(DEFAULT_FROM_EMAIL="info@open-forms.nl") def test_submission_with_email_backend_export_csv_xlsx(self): email_form_options = dict(