diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 15f1ccb79..b524f9d0a 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -21,7 +21,12 @@ from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.user_domain_role import UserDomainRole
from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch
-from registrar.utility.admin_helpers import get_all_action_needed_reason_emails, get_action_needed_reason_default_email
+from registrar.utility.admin_helpers import (
+ get_all_action_needed_reason_emails,
+ get_action_needed_reason_default_email,
+ get_all_rejection_reason_emails,
+ get_rejection_reason_default_email,
+)
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
from registrar.utility.constants import BranchChoices
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
@@ -234,6 +239,7 @@ class DomainRequestAdminForm(forms.ModelForm):
}
labels = {
"action_needed_reason_email": "Email",
+ "rejection_reason_email": "Email",
}
def __init__(self, *args, **kwargs):
@@ -1755,6 +1761,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
"status_history",
"status",
"rejection_reason",
+ "rejection_reason_email",
"action_needed_reason",
"action_needed_reason_email",
"investigator",
@@ -1938,24 +1945,16 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
original_obj = models.DomainRequest.objects.get(pk=obj.pk)
# == Handle action_needed_reason == #
+ action_needed_reason_changed = obj.action_needed_reason != original_obj.action_needed_reason
+ if action_needed_reason_changed:
+ obj = self._handle_action_needed_reason(request, obj, original_obj)
- reason_changed = obj.action_needed_reason != original_obj.action_needed_reason
- if reason_changed:
- # Track the fact that we sent out an email
- request.session["action_needed_email_sent"] = True
-
- # Set the action_needed_reason_email to the default if nothing exists.
- # Since this check occurs after save, if the user enters a value then we won't update.
-
- default_email = get_action_needed_reason_default_email(request, obj, obj.action_needed_reason)
- if obj.action_needed_reason_email:
- emails = get_all_action_needed_reason_emails(request, obj)
- is_custom_email = obj.action_needed_reason_email not in emails.values()
- if not is_custom_email:
- obj.action_needed_reason_email = default_email
- else:
- obj.action_needed_reason_email = default_email
+ # == Handle rejection_reason == #
+ rejection_reason_changed = obj.rejection_reason != original_obj.rejection_reason
+ if rejection_reason_changed:
+ obj = self._handle_rejection_reason(request, obj, original_obj)
+ # == Handle allowed emails == #
if obj.status in DomainRequest.get_statuses_that_send_emails() and not settings.IS_PRODUCTION:
self._check_for_valid_email(request, obj)
@@ -1971,6 +1970,40 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
if should_save:
return super().save_model(request, obj, form, change)
+ def _handle_action_needed_reason(self, request, obj, original_obj):
+ # Track the fact that we sent out an email
+ request.session["action_needed_email_sent"] = True
+
+ # Set the action_needed_reason_email to the default if nothing exists.
+ # Since this check occurs after save, if the user enters a value then we won't update.
+
+ default_email = get_action_needed_reason_default_email(obj, obj.action_needed_reason)
+ if obj.action_needed_reason_email:
+ emails = get_all_action_needed_reason_emails(obj)
+ is_custom_email = obj.action_needed_reason_email not in emails.values()
+ if not is_custom_email:
+ obj.action_needed_reason_email = default_email
+ else:
+ obj.action_needed_reason_email = default_email
+ return obj
+
+ def _handle_rejection_reason(self, request, obj, original_obj):
+ # Track the fact that we sent out an email
+ request.session["rejection_reason_email_sent"] = True
+
+ # Set the rejection_reason_email to the default if nothing exists.
+ # Since this check occurs after save, if the user enters a value then we won't update.
+
+ default_email = get_rejection_reason_default_email(obj, obj.action_needed_reason)
+ if obj.rejection_reason_email:
+ emails = get_all_rejection_reason_emails(obj)
+ is_custom_email = obj.rejection_reason_email not in emails.values()
+ if not is_custom_email:
+ obj.rejection_reason_email = default_email
+ else:
+ obj.rejection_reason_email = default_email
+ return obj
+
def _check_for_valid_email(self, request, obj):
"""Certain emails are whitelisted in non-production environments,
so we should display that information using this function.
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index 7e3c086c4..7a7b63ba4 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -500,83 +500,41 @@ function initializeWidgetOnList(list, parentId) {
}
})();
+class CustomizableEmailBase {
+ constructor(dropdown, textarea, textareaPlaceholder, directEditButton, modalTrigger, modalConfirm, formLabel, lastSentEmailContent, apiUrl) {
+ this.dropdown = dropdown;
+ this.textarea = textarea;
+ this.textareaPlaceholder = textareaPlaceholder;
+ this.directEditButton = directEditButton;
+ this.modalTrigger = modalTrigger;
+ this.modalConfirm = modalConfirm;
+ this.formLabel = formLabel;
+ this.lastSentEmailContent = lastSentEmailContent;
+ this.apiUrl = apiUrl;
-/** An IIFE that hooks to the show/hide button underneath action needed reason.
- * This shows the auto generated email on action needed reason.
-*/
-document.addEventListener('DOMContentLoaded', function() {
- const dropdown = document.getElementById("id_action_needed_reason");
- const textarea = document.getElementById("id_action_needed_reason_email")
- const domainRequestId = dropdown ? document.getElementById("domain_request_id").value : null
- const textareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder");
- const directEditButton = document.querySelector('.field-action_needed_reason_email__edit');
- const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger');
- const modalConfirm = document.getElementById('confirm-edit-email');
- const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]');
- let lastSentEmailContent = document.getElementById("last-sent-email-content");
- const initialDropdownValue = dropdown ? dropdown.value : null;
- const initialEmailValue = textarea.value;
+ this.domainRequestId = this.dropdown ? document.getElementById("domain_request_id").value : null
+ this.initialDropdownValue = this.dropdown ? this.dropdown.value : null;
+ this.initialEmailValue = this.textarea ? this.textarea.value : null;
- // We will use the const to control the modal
- let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
- // We will use the function to control the label and help
- function isEmailAlreadySent() {
- return lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
- }
-
- if (!dropdown || !textarea || !domainRequestId || !formLabel || !modalConfirm) return;
- const apiUrl = document.getElementById("get-action-needed-email-for-user-json").value;
-
- function updateUserInterface(reason) {
- if (!reason) {
- // No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text
- formLabel.innerHTML = "Email:";
- textareaPlaceholder.innerHTML = "Select an action needed reason to see email";
- showElement(textareaPlaceholder);
- hideElement(directEditButton);
- hideElement(modalTrigger);
- hideElement(textarea);
- } else if (reason === 'other') {
- // 'Other' selected, we will set the label to "Email", show the "No email will be sent" placeholder, hide the trigger, textarea, hide the help text
- formLabel.innerHTML = "Email:";
- textareaPlaceholder.innerHTML = "No email will be sent";
- showElement(textareaPlaceholder);
- hideElement(directEditButton);
- hideElement(modalTrigger);
- hideElement(textarea);
- } else {
- // A triggering selection is selected, all hands on board:
- textarea.setAttribute('readonly', true);
- showElement(textarea);
- hideElement(textareaPlaceholder);
-
- if (isEmailAlreadySentConst) {
- hideElement(directEditButton);
- showElement(modalTrigger);
- } else {
- showElement(directEditButton);
- hideElement(modalTrigger);
- }
- if (isEmailAlreadySent()) {
- formLabel.innerHTML = "Email sent to creator:";
- } else {
- formLabel.innerHTML = "Email:";
- }
+ this.isEmailAlreadySentConst;
+ if (lastSentEmailContent && textarea) {
+ this.isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
}
}
- // Initialize UI
- updateUserInterface(dropdown.value);
-
- dropdown.addEventListener("change", function() {
- const reason = dropdown.value;
- // Update the UI
- updateUserInterface(reason);
- if (reason && reason !== "other") {
- // If it's not the initial value
- if (initialDropdownValue !== dropdown.value || initialEmailValue !== textarea.value) {
+ initializeDropdown(errorMessage) {
+ this.dropdown.addEventListener("change", () => {
+ console.log(this.dropdown)
+ let reason = this.dropdown.value;
+ if (this.initialDropdownValue !== this.dropdown.value || this.initialEmailValue !== this.textarea.value) {
+ let searchParams = new URLSearchParams(
+ {
+ "reason": reason,
+ "domain_request_id": this.domainRequestId,
+ }
+ );
// Replace the email content
- fetch(`${apiUrl}?reason=${reason}&domain_request_id=${domainRequestId}`)
+ fetch(`${this.apiUrl}?${searchParams.toString()}`)
.then(response => {
return response.json().then(data => data);
})
@@ -584,30 +542,174 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.error) {
console.error("Error in AJAX call: " + data.error);
}else {
- textarea.value = data.action_needed_email;
+ this.textarea.value = data.action_needed_email;
}
- updateUserInterface(reason);
+ this.updateUserInterface(reason);
})
.catch(error => {
- console.error("Error action needed email: ", error)
+ console.error(errorMessage, error)
});
}
+ });
+ }
+
+ initializeModalConfirm() {
+ this.modalConfirm.addEventListener("click", () => {
+ this.textarea.removeAttribute('readonly');
+ this.textarea.focus();
+ hideElement(this.directEditButton);
+ hideElement(this.modalTrigger);
+ });
+ }
+
+ initializeDirectEditButton() {
+ this.directEditButton.addEventListener("click", () => {
+ this.textarea.removeAttribute('readonly');
+ this.textarea.focus();
+ hideElement(this.directEditButton);
+ hideElement(this.modalTrigger);
+ });
+ }
+
+ isEmailAlreadySent() {
+ return this.lastSentEmailContent.value.replace(/\s+/g, '') === this.textarea.value.replace(/\s+/g, '');
+ }
+
+ updateUserInterface(reason) {
+ if (!reason) {
+ // No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text
+ this.showPlaceholder("Email:", "Select an action needed reason to see email");
+ } else if (reason === 'other') {
+ // 'Other' selected, we will set the label to "Email", show the "No email will be sent" placeholder, hide the trigger, textarea, hide the help text
+ this.showPlaceholder("Email:", "No email will be sent");
+ } else {
+ // A triggering selection is selected, all hands on board:
+ this.textarea.setAttribute('readonly', true);
+ showElement(this.textarea);
+ hideElement(this.textareaPlaceholder);
+
+ if (this.isEmailAlreadySentConst) {
+ hideElement(this.directEditButton);
+ showElement(this.modalTrigger);
+ } else {
+ showElement(this.directEditButton);
+ hideElement(this.modalTrigger);
+ }
+
+ if (this.isEmailAlreadySent()) {
+ this.formLabel.innerHTML = "Email sent to creator:";
+ } else {
+ this.formLabel.innerHTML = "Email:";
+ }
}
+ }
- });
+ showPlaceholder(formLabelText, placeholderText) {
+ this.formLabel.innerHTML = formLabelText;
+ this.textareaPlaceholder.innerHTML = placeholderText;
+ showElement(this.textareaPlaceholder);
+ hideElement(this.directEditButton);
+ hideElement(this.modalTrigger);
+ hideElement(this.textarea);
+ }
+}
- modalConfirm.addEventListener("click", () => {
- textarea.removeAttribute('readonly');
- textarea.focus();
- hideElement(directEditButton);
- hideElement(modalTrigger);
- });
- directEditButton.addEventListener("click", () => {
- textarea.removeAttribute('readonly');
- textarea.focus();
- hideElement(directEditButton);
- hideElement(modalTrigger);
- });
+
+
+class customActionNeededEmail extends CustomizableEmailBase {
+ constructor() {
+ const dropdown = document.getElementById("id_action_needed_reason");
+ const textarea = document.getElementById("id_action_needed_reason_email")
+ const textareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder");
+ const directEditButton = document.querySelector('.field-action_needed_reason_email__edit');
+ const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger');
+ const modalConfirm = document.getElementById('confirm-edit-email');
+ const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]');
+ const lastSentEmailContent = document.getElementById("last-sent-email-content");
+
+ let apiContainer = document.getElementById("get-action-needed-email-for-user-json")
+ const apiUrl = apiContainer ? apiContainer.value : null;
+ super(
+ dropdown,
+ textarea,
+ textareaPlaceholder,
+ directEditButton,
+ modalTrigger,
+ modalConfirm,
+ formLabel,
+ lastSentEmailContent,
+ apiUrl
+ );
+ }
+
+ loadActionNeededEmail() {
+ this.updateUserInterface(this.dropdown.value);
+ this.initializeDropdown("Error when attempting to grab action needed email: ")
+ this.initializeModalConfirm()
+ this.initializeDirectEditButton()
+ }
+}
+
+/** An IIFE that hooks to the show/hide button underneath action needed reason.
+ * This shows the auto generated email on action needed reason.
+*/
+document.addEventListener('DOMContentLoaded', function() {
+ const customEmail = new customActionNeededEmail();
+ if (!customEmail.dropdown || !customEmail.textarea || !customEmail.domainRequestId || !customEmail.formLabel || !customEmail.modalConfirm){
+ return;
+ }
+
+ // Initialize UI
+ customEmail.loadActionNeededEmail()
+});
+
+
+class customRejectedEmail extends CustomizableEmailBase {
+ constructor() {
+ const dropdown = document.getElementById("id_rejection_reason");
+ const textarea = document.getElementById("id_rejection_reason_email")
+ const textareaPlaceholder = document.querySelector(".field-rejection_reason_email__placeholder");
+ const directEditButton = document.querySelector('.field-rejection_reason_email__edit');
+ const modalTrigger = document.querySelector('.field-rejection_reason_email__modal-trigger');
+ const modalConfirm = document.getElementById('confirm-edit-email');
+ const formLabel = document.querySelector('label[for="id_rejection_reason_email"]');
+ const lastSentEmailContent = document.getElementById("last-sent-email-content");
+
+ let apiContainer = document.getElementById("get-rejection-reason-email-for-user-json")
+ const apiUrl = apiContainer ? apiContainer.value : null;
+ super(
+ dropdown,
+ textarea,
+ textareaPlaceholder,
+ directEditButton,
+ modalTrigger,
+ modalConfirm,
+ formLabel,
+ lastSentEmailContent,
+ apiUrl
+ );
+ }
+
+ loadRejectedEmail() {
+ this.updateUserInterface(this.dropdown.value);
+ this.initializeDropdown("Error when attempting to grab rejected email: ")
+ this.initializeModalConfirm()
+ this.initializeDirectEditButton()
+ }
+}
+
+
+/** An IIFE that hooks to the show/hide button underneath rejected reason.
+ * This shows the auto generated email on action needed reason.
+*/
+document.addEventListener('DOMContentLoaded', function() {
+ const customEmail = new customRejectedEmail();
+ if (!customEmail.dropdown || !customEmail.textarea || !customEmail.domainRequestId || !customEmail.formLabel || !customEmail.modalConfirm){
+ return;
+ }
+
+ // Initialize UI
+ customEmail.loadRejectedEmail()
});
diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py
index bb8693ac1..b1cc00bde 100644
--- a/src/registrar/models/domain_request.py
+++ b/src/registrar/models/domain_request.py
@@ -300,6 +300,11 @@ class DomainRequest(TimeStampedModel):
blank=True,
)
+ rejection_reason_email = models.TextField(
+ null=True,
+ blank=True,
+ )
+
action_needed_reason = models.TextField(
choices=ActionNeededReasons.choices,
null=True,
@@ -798,6 +803,21 @@ class DomainRequest(TimeStampedModel):
except EmailSendingError:
logger.warning("Failed to send confirmation email", exc_info=True)
+ def _send_custom_status_update_email(self, email_content):
+ """Wrapper for `_send_status_update_email` that bcc's help@get.gov
+ and sends an email equivalent to the 'email_content' variable."""
+ if settings.IS_PRODUCTION:
+ bcc_address = settings.DEFAULT_FROM_EMAIL
+
+ self._send_status_update_email(
+ new_status="action needed",
+ email_template=f"emails/includes/custom_email.txt",
+ email_template_subject=f"emails/status_change_subject.txt",
+ bcc_address=bcc_address,
+ custom_email_content=email_content,
+ wrap_email=True,
+ )
+
def investigator_exists_and_is_staff(self):
"""Checks if the current investigator is in a valid state for a state transition"""
is_valid = True
@@ -901,7 +921,7 @@ class DomainRequest(TimeStampedModel):
target=DomainRequestStatus.ACTION_NEEDED,
conditions=[domain_is_not_active, investigator_exists_and_is_staff],
)
- def action_needed(self, send_email=True):
+ def action_needed(self):
"""Send back an domain request that is under investigation or rejected.
This action is logged.
@@ -924,27 +944,7 @@ class DomainRequest(TimeStampedModel):
# Send out an email if an action needed reason exists
if self.action_needed_reason and self.action_needed_reason != self.ActionNeededReasons.OTHER:
email_content = self.action_needed_reason_email
- self._send_action_needed_reason_email(send_email, email_content)
-
- def _send_action_needed_reason_email(self, send_email=True, email_content=None):
- """Sends out an automatic email for each valid action needed reason provided"""
-
- email_template_name = "custom_email.txt"
- email_template_subject_name = f"{self.action_needed_reason}_subject.txt"
-
- bcc_address = ""
- if settings.IS_PRODUCTION:
- bcc_address = settings.DEFAULT_FROM_EMAIL
-
- self._send_status_update_email(
- new_status="action needed",
- email_template=f"emails/action_needed_reasons/{email_template_name}",
- email_template_subject=f"emails/action_needed_reasons/{email_template_subject_name}",
- send_email=send_email,
- bcc_address=bcc_address,
- custom_email_content=email_content,
- wrap_email=True,
- )
+ self._send_custom_status_update_email(email_content)
@transition(
field="status",
@@ -1051,6 +1051,11 @@ class DomainRequest(TimeStampedModel):
"emails/status_change_rejected_subject.txt",
)
+ # Send out an email if a rejection reason exists
+ if self.rejection_reason:
+ email_content = self.rejection_reason_email
+ self._send_custom_status_update_email(email_content)
+
@transition(
field="status",
source=[
diff --git a/src/registrar/templates/emails/action_needed_reasons/custom_email.txt b/src/registrar/templates/emails/includes/custom_email.txt
similarity index 100%
rename from src/registrar/templates/emails/action_needed_reasons/custom_email.txt
rename to src/registrar/templates/emails/includes/custom_email.txt
diff --git a/src/registrar/templates/emails/includes/email_footer.txt b/src/registrar/templates/emails/includes/email_footer.txt
new file mode 100644
index 000000000..f10d82a91
--- /dev/null
+++ b/src/registrar/templates/emails/includes/email_footer.txt
@@ -0,0 +1,10 @@
+THANK YOU
+.Gov helps the public identify official, trusted information. Thank you for requesting a .gov domain.
+
+----------------------------------------------------------------
+
+The .gov team
+Contact us:
+Learn about .gov
+
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
\ No newline at end of file
diff --git a/src/registrar/templates/emails/includes/status_change_rejected_header.txt b/src/registrar/templates/emails/includes/status_change_rejected_header.txt
new file mode 100644
index 000000000..16b7c73a9
--- /dev/null
+++ b/src/registrar/templates/emails/includes/status_change_rejected_header.txt
@@ -0,0 +1,8 @@
+Hi, {{ recipient.first_name }}.
+
+Your .gov domain request has been rejected.
+
+DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
+REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
+STATUS: Rejected
+----------------------------------------------------------------
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/contacts_not_verified.txt b/src/registrar/templates/emails/rejection_reasons/contacts_not_verified.txt
new file mode 100644
index 000000000..c35c82c2b
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/contacts_not_verified.txt
@@ -0,0 +1,8 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+REJECTION REASON
+Your domain request was rejected because we could not verify the organizational
+contacts you provided. If you have questions or comments, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/naming_not_met.txt b/src/registrar/templates/emails/rejection_reasons/naming_not_met.txt
new file mode 100644
index 000000000..3e57d579d
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/naming_not_met.txt
@@ -0,0 +1,15 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+REJECTION REASON
+Your domain request was rejected because it does not meet our naming requirements.
+Domains should uniquely identify a government organization and be clear to the
+general public. Learn more about naming requirements for your type of organization
+.
+
+
+YOU CAN SUBMIT A NEW REQUEST
+We encourage you to request a domain that meets our requirements. If you have
+questions or want to discuss potential domain names, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/org_has_domain.txt b/src/registrar/templates/emails/rejection_reasons/org_has_domain.txt
new file mode 100644
index 000000000..26757efd6
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/org_has_domain.txt
@@ -0,0 +1,15 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+REJECTION REASON
+Your domain request was rejected because {{ domain_request.organization_name }} has a .gov domain. Our
+practice is to approve one domain per online service per government organization. We
+evaluate additional requests on a case-by-case basis. You did not provide sufficient
+justification for an additional domain.
+
+Read more about our practice of approving one domain per online service
+.
+
+If you have questions or comments, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/org_not_eligible.txt b/src/registrar/templates/emails/rejection_reasons/org_not_eligible.txt
new file mode 100644
index 000000000..3c7de3f42
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/org_not_eligible.txt
@@ -0,0 +1,14 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+REJECTION REASON
+Your domain request was rejected because we determined that {{ domain_request.organization_name }} is not
+eligible for a .gov domain. .Gov domains are only available to official U.S.-based
+government organizations.
+
+Learn more about eligibility for .gov domains
+.
+
+If you have questions or comments, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/other.txt b/src/registrar/templates/emails/rejection_reasons/other.txt
new file mode 100644
index 000000000..6835a45e0
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/other.txt
@@ -0,0 +1,15 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+YOU CAN SUBMIT A NEW REQUEST
+If your organization is eligible for a .gov domain and you meet our other requirements, you can submit a new request.
+
+Learn more about:
+- Eligibility for a .gov domain
+- Choosing a .gov domain name
+
+
+NEED ASSISTANCE?
+If you have questions about this domain request or need help choosing a new domain name, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/purpose_not_met.txt b/src/registrar/templates/emails/rejection_reasons/purpose_not_met.txt
new file mode 100644
index 000000000..57bce78f0
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/purpose_not_met.txt
@@ -0,0 +1,15 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+REJECTION REASON
+Your domain request was rejected because the purpose you provided did not meet our
+requirements. You didn’t provide enough information about how you intend to use the
+domain.
+
+Learn more about:
+- Eligibility for a .gov domain
+- What you can and can’t do with .gov domains
+
+If you have questions or comments, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/rejection_reasons/requestor_not_eligible.txt b/src/registrar/templates/emails/rejection_reasons/requestor_not_eligible.txt
new file mode 100644
index 000000000..7974c1690
--- /dev/null
+++ b/src/registrar/templates/emails/rejection_reasons/requestor_not_eligible.txt
@@ -0,0 +1,14 @@
+{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
+{% include "emails/includes/status_change_rejected_header.html" %}
+REJECTION REASON
+Your domain request was rejected because we don’t believe you’re eligible to request a
+.gov domain on behalf of {{ domain_request.organization_name }}. You must be a government employee, or be
+working on behalf of a government organization, to request a .gov domain.
+
+
+DEMONSTRATE ELIGIBILITY
+If you can provide more information that demonstrates your eligibility, or you want to
+discuss further, reply to this email.
+
+{% include "emails/includes/email_footer.html" %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/src/registrar/templates/emails/status_change_rejected_subject.txt b/src/registrar/templates/emails/status_change_subject.txt
similarity index 100%
rename from src/registrar/templates/emails/status_change_rejected_subject.txt
rename to src/registrar/templates/emails/status_change_subject.txt
diff --git a/src/registrar/utility/admin_helpers.py b/src/registrar/utility/admin_helpers.py
index 0b99bba13..87edc2106 100644
--- a/src/registrar/utility/admin_helpers.py
+++ b/src/registrar/utility/admin_helpers.py
@@ -2,23 +2,66 @@ from registrar.models.domain_request import DomainRequest
from django.template.loader import get_template
-def get_all_action_needed_reason_emails(request, domain_request):
+def get_all_action_needed_reason_emails(domain_request):
"""Returns a dictionary of every action needed reason and its associated email
for this particular domain request."""
+ return _get_all_default_emails(
+ reasons=DomainRequest.ActionNeededReasons,
+ # Where the emails are stored. This assumes that it contains a list of .txt files with the reason.
+ path_root="emails/action_needed_reasons",
+ # What reasons don't send out emails (none is handled automagically)
+ excluded_reasons=[DomainRequest.ActionNeededReasons.OTHER],
+ # Who to send it to, and from what domain request to reference
+ domain_request=domain_request
+ )
+
+def get_action_needed_reason_default_email(domain_request, action_needed_reason):
+ """Returns the default email associated with the given action needed reason"""
+ return _get_default_email(
+ domain_request,
+ path_root="emails/rejection_reasons",
+ reason=action_needed_reason,
+ excluded_reasons=[DomainRequest.ActionNeededReasons.OTHER]
+ )
+
+
+def get_all_rejection_reason_emails(domain_request):
+ """Returns a dictionary of every rejection reason and its associated email
+ for this particular domain request."""
+ return _get_all_default_emails(
+ reasons=DomainRequest.RejectionReasons,
+ # Where the emails are stored. This assumes that it contains a list of .txt files with the reason.
+ path_root="emails/rejection_reasons",
+ # What reasons don't send out emails (none is handled automagically)
+ excluded_reasons=[DomainRequest.RejectionReasons.OTHER],
+ # Who to send it to, and from what domain request to reference
+ domain_request=domain_request
+ )
+
+
+def get_rejection_reason_default_email(domain_request, action_needed_reason):
+ """Returns the default email associated with the given rejection reason"""
+ return _get_default_email(
+ domain_request,
+ path_root="emails/rejection_reasons",
+ reason=action_needed_reason,
+ excluded_reasons=[DomainRequest.RejectionReasons.OTHER]
+ )
+
+def _get_all_default_emails(reasons, path_root, excluded_reasons, domain_request):
emails = {}
- for action_needed_reason in domain_request.ActionNeededReasons:
- # Map the action_needed_reason to its default email
- emails[action_needed_reason.value] = get_action_needed_reason_default_email(
- request, domain_request, action_needed_reason.value
+ for reason in reasons:
+ # Map the reason to its default email
+ emails[reason.value] = _get_default_email(
+ domain_request, path_root, reason, excluded_reasons
)
- return emails
+def _get_default_email(domain_request, path_root, reason, excluded_reasons=None):
+ if not reason:
+ return None
-
-def get_action_needed_reason_default_email(request, domain_request, action_needed_reason):
- """Returns the default email associated with the given action needed reason"""
- if not action_needed_reason or action_needed_reason == DomainRequest.ActionNeededReasons.OTHER:
+ if excluded_reasons and reason in excluded_reasons:
return None
recipient = domain_request.creator
@@ -26,7 +69,7 @@ def get_action_needed_reason_default_email(request, domain_request, action_neede
context = {"domain_request": domain_request, "recipient": recipient}
# Get the email body
- template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt"
+ template_path = f"{path_root}/{reason}.txt"
email_body_text = get_template(template_path).render(context=context)
email_body_text_cleaned = None
diff --git a/src/registrar/views/utility/api_views.py b/src/registrar/views/utility/api_views.py
index 6a6269baa..973f85855 100644
--- a/src/registrar/views/utility/api_views.py
+++ b/src/registrar/views/utility/api_views.py
@@ -90,5 +90,5 @@ def get_action_needed_email_for_user_json(request):
return JsonResponse({"error": "No domain_request_id specified"}, status=404)
domain_request = DomainRequest.objects.filter(id=domain_request_id).first()
- emails = get_all_action_needed_reason_emails(request, domain_request)
+ emails = get_all_action_needed_reason_emails(domain_request)
return JsonResponse({"action_needed_email": emails.get(reason)}, status=200)