Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic email registration recipients using variables #4971

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
12 changes: 12 additions & 0 deletions src/openforms/js/compiled-lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5457,6 +5457,12 @@
"value": "the variable"
}
],
"mOh/pF": [
{
"type": 0,
"value": "The email address described in this variable will be used for the mailing. If a variable is selected, the general registration addresses will be used as fallback option."
}
],
"mOjKcm": [
{
"type": 0,
Expand Down Expand Up @@ -6273,6 +6279,12 @@
"value": "Do not display the configured label and top line as the header in the fieldset."
}
],
"uwCD8e": [
{
"type": 0,
"value": "Using a variable to decide to which email address the submission details will be sent"
}
],
"vAZDY4": [
{
"type": 0,
Expand Down
12 changes: 12 additions & 0 deletions src/openforms/js/compiled-lang/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -5475,6 +5475,12 @@
"value": "de variabele"
}
],
"mOh/pF": [
{
"type": 0,
"value": "The email address described in this variable will be used for the mailing. If a variable is selected, the general registration addresses will be used as fallback option."
}
],
"mOjKcm": [
{
"type": 0,
Expand Down Expand Up @@ -6291,6 +6297,12 @@
"value": "Verberg de koptekst en de lijn boven de veldengroep in het formulier."
}
],
"uwCD8e": [
{
"type": 0,
"value": "Using a variable to decide to which email address the submission details will be sent"
}
],
"vAZDY4": [
{
"type": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ EmailOptionsForm.propTypes = {
emailSubject: PropTypes.string,
paymentEmails: PropTypes.arrayOf(PropTypes.string),
toEmails: PropTypes.arrayOf(PropTypes.string),
toEmailsFromVariable: PropTypes.string,
}),
onChange: PropTypes.func.isRequired,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import EmailHasAttachmentSelect from './fields/EmailHasAttachmentSelect';
import EmailPaymentSubject from './fields/EmailPaymentSubject';
import EmailPaymentUpdateRecipients from './fields/EmailPaymentUpdateRecipients';
import EmailRecipients from './fields/EmailRecipients';
import EmailRecipientsFromVariable from './fields/EmailRecipientsFromVariable';
import EmailSubject from './fields/EmailSubject';

const EmailOptionsFormFields = ({name, schema}) => {
Expand All @@ -37,6 +38,7 @@ const EmailOptionsFormFields = ({name, schema}) => {
<ValidationErrorsProvider errors={relevantErrors}>
<Fieldset>
<EmailRecipients />
<EmailRecipientsFromVariable />
<EmailSubject />
<EmailContentTemplateHTML />
<EmailContentTemplateText />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {useField} from 'formik';
import React from 'react';
import {FormattedMessage} from 'react-intl';

import Field from 'components/admin/forms/Field';
import FormRow from 'components/admin/forms/FormRow';
import VariableSelection from 'components/admin/forms/VariableSelection';

const EmailRecipientsFromVariable = () => {
const [fieldProps, , fieldHelpers] = useField('toEmailsFromVariable');
const {setValue} = fieldHelpers;
return (
<FormRow>
<Field
name="toEmailsFromVariable"
label={
<FormattedMessage
description="Email registration options 'toEmailsFromVariable' label"
defaultMessage="Using a variable to decide to which email address the
submission details will be sent"
/>
}
helpText={
<FormattedMessage
description="Email registration options 'toEmailsFromVariable' helpText"
defaultMessage="The email address described in this variable will be used
for the mailing. If a variable is selected, the general registration
addresses will be used as fallback option. "
/>
}
>
<VariableSelection
name="toEmailsFromVariable"
value={fieldProps.value}
onChange={event => {
setValue(event.target.value);
}}
/>
</Field>
</FormRow>
);
};

EmailRecipientsFromVariable.propTypes = {};

export default EmailRecipientsFromVariable;
10 changes: 10 additions & 0 deletions src/openforms/js/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2524,6 +2524,11 @@
"description": "\"variable\" operand type",
"originalDefault": "the variable"
},
"mOh/pF": {
"defaultMessage": "The email address described in this variable will be used for the mailing. If a variable is selected, the general registration addresses will be used as fallback option.",
"description": "Email registration options 'toEmailsFromVariable' helpText",
"originalDefault": "The email address described in this variable will be used for the mailing. If a variable is selected, the general registration addresses will be used as fallback option."
},
"mOjKcm": {
"defaultMessage": "Steps and fields",
"description": "Form design tab title",
Expand Down Expand Up @@ -2914,6 +2919,11 @@
"description": "History link button",
"originalDefault": "History"
},
"uwCD8e": {
"defaultMessage": "Using a variable to decide to which email address the submission details will be sent",
"description": "Email registration options 'toEmailsFromVariable' label",
"originalDefault": "Using a variable to decide to which email address the submission details will be sent"
},
"vAZDY4": {
"defaultMessage": "Source path",
"description": "Prefill / Objects API: column header for object type property selection",
Expand Down
10 changes: 10 additions & 0 deletions src/openforms/js/lang/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,11 @@
"description": "\"variable\" operand type",
"originalDefault": "the variable"
},
"mOh/pF": {
"defaultMessage": "The email address described in this variable will be used for the mailing. If a variable is selected, the general registration addresses will be used as fallback option.",
"description": "Email registration options 'toEmailsFromVariable' helpText",
"originalDefault": "The email address described in this variable will be used for the mailing. If a variable is selected, the general registration addresses will be used as fallback option."
},
"mOjKcm": {
"defaultMessage": "Stappen en velden",
"description": "Form design tab title",
Expand Down Expand Up @@ -2935,6 +2940,11 @@
"description": "History link button",
"originalDefault": "History"
},
"uwCD8e": {
"defaultMessage": "Using a variable to decide to which email address the submission details will be sent",
"description": "Email registration options 'toEmailsFromVariable' label",
"originalDefault": "Using a variable to decide to which email address the submission details will be sent"
},
"vAZDY4": {
"defaultMessage": "Bronpad",
"description": "Prefill / Objects API: column header for object type property selection",
Expand Down
59 changes: 42 additions & 17 deletions src/openforms/registrations/contrib/email/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,40 @@
from .constants import AttachmentFormat


class Options(TypedDict):
"""
Shape of the email registration plugin options.

This describes the shape of :attr:`EmailOptionsSerializer.validated_data`, after
the input data has been cleaned/validated.
"""

to_emails: NotRequired[list[str]]
to_emails_from_variable: NotRequired[str]
attachment_formats: NotRequired[list[AttachmentFormat | str]]
payment_emails: NotRequired[list[str]]
attach_files_to_email: bool | None
email_subject: NotRequired[str]
email_payment_subject: NotRequired[str]
email_content_template_html: NotRequired[str]
email_content_template_text: NotRequired[str]


class EmailOptionsSerializer(JsonSchemaSerializerMixin, serializers.Serializer):
to_emails = serializers.ListField(
child=serializers.EmailField(),
label=_("The email addresses to which the submission details will be sent"),
required=True,
required=False, # Either to_emails or to_emails_from_variable should be required
)
to_emails_from_variable = serializers.CharField(
label=_("Key of the target variable containing the email address"),
required=False,
allow_blank=True,
help_text=_(
"Key of the target variable whose value will be used for the mailing. "
"When using this field, the mailing will only be send to this email address. "
"The email addresses field would then be ignored. "
),
)
attachment_formats = serializers.ListField(
child=serializers.ChoiceField(choices=AttachmentFormat.choices),
Expand Down Expand Up @@ -99,23 +128,19 @@ class Meta:
),
]

def validate(self, attrs: Options) -> Options:
# The email registration requires either `to_emails` or `to_emails_from_variable`
# to determine which email address to use.
# Both may be set - in that case, `to_emails_from_variable` is preferred.
if not attrs.get("to_emails") and not attrs.get("to_emails_from_variable"):
raise serializers.ValidationError(
{
"to_emails": _("This field is required."),
},
code="required",
)

class Options(TypedDict):
"""
Shape of the email registration plugin options.

This describes the shape of :attr:`EmailOptionsSerializer.validated_data`, after
the input data has been cleaned/validated.
"""

to_emails: list[str]
attachment_formats: NotRequired[list[AttachmentFormat | str]]
payment_emails: NotRequired[list[str]]
attach_files_to_email: bool | None
email_subject: NotRequired[str]
email_payment_subject: NotRequired[str]
email_content_template_html: NotRequired[str]
email_content_template_text: NotRequired[str]
return attrs


# sanity check for development - keep serializer and type definitions in sync
Expand Down
37 changes: 35 additions & 2 deletions src/openforms/registrations/contrib/email/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down
Loading
Loading