diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index a5a2132dc0c..9e24a26caa8 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -38,18 +38,18 @@ class EmailCampaign(Document): def set_date(self): if getdate(self.start_date) < getdate(today()): frappe.throw(_("Start Date cannot be before the current date")) + # set the end date as start date + max(send after days) in campaign schedule - send_after_days = [] - campaign = frappe.get_doc("Campaign", self.campaign_name) - for entry in campaign.get("campaign_schedules"): - send_after_days.append(entry.send_after_days) - try: - self.end_date = add_days(getdate(self.start_date), max(send_after_days)) - except ValueError: + campaign = frappe.get_cached_doc("Campaign", self.campaign_name) + send_after_days = [entry.send_after_days for entry in campaign.get("campaign_schedules")] + + if not send_after_days: frappe.throw( _("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name) ) + self.end_date = add_days(getdate(self.start_date), max(send_after_days)) + def validate_lead(self): lead_email_id = frappe.db.get_value("Lead", self.recipient, "email_id") if not lead_email_id: @@ -77,58 +77,128 @@ class EmailCampaign(Document): start_date = getdate(self.start_date) end_date = getdate(self.end_date) today_date = getdate(today()) + if start_date > today_date: - self.db_set("status", "Scheduled", update_modified=False) + new_status = "Scheduled" elif end_date >= today_date: - self.db_set("status", "In Progress", update_modified=False) - elif end_date < today_date: - self.db_set("status", "Completed", update_modified=False) + new_status = "In Progress" + else: + new_status = "Completed" + + if self.status != new_status: + self.db_set("status", new_status, update_modified=False) # called through hooks to send campaign mails to leads def send_email_to_leads_or_contacts(): + today_date = getdate(today()) + + # Get all active email campaigns in a single query email_campaigns = frappe.get_all( - "Email Campaign", filters={"status": ("not in", ["Unsubscribed", "Completed", "Scheduled"])} + "Email Campaign", + filters={"status": "In Progress"}, + fields=["name", "campaign_name", "email_campaign_for", "recipient", "start_date", "sender"], ) - for camp in email_campaigns: - email_campaign = frappe.get_doc("Email Campaign", camp.name) - campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name) + + if not email_campaigns: + return + + # Process each email campaign + for email_campaign in email_campaigns: + try: + campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name) + except frappe.DoesNotExistError: + frappe.log_error( + title=_("Email Campaign Error"), + message=_("Campaign {0} not found").format(email_campaign.campaign_name), + ) + continue + + # Find schedules that match today for entry in campaign.get("campaign_schedules"): - scheduled_date = add_days(email_campaign.get("start_date"), entry.get("send_after_days")) - if scheduled_date == getdate(today()): - send_mail(entry, email_campaign) + try: + scheduled_date = add_days(getdate(email_campaign.start_date), entry.get("send_after_days")) + if scheduled_date == today_date: + send_mail(entry, email_campaign) + except Exception: + frappe.log_error( + title=_("Email Campaign Send Error"), + message=_("Failed to send email for campaign {0} to {1}").format( + email_campaign.name, email_campaign.recipient + ), + ) def send_mail(entry, email_campaign): - recipient_list = [] - if email_campaign.email_campaign_for == "Email Group": - for member in frappe.db.get_list( - "Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"] - ): - recipient_list.append(member["email"]) + campaign_for = email_campaign.get("email_campaign_for") + recipient = email_campaign.get("recipient") + sender_user = email_campaign.get("sender") + campaign_name = email_campaign.get("name") + + # Get recipient emails + if campaign_for == "Email Group": + recipient_list = frappe.get_all( + "Email Group Member", + filters={"email_group": recipient, "unsubscribed": 0}, + pluck="email", + ) else: - recipient_list.append( - frappe.db.get_value( - email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id" + email_id = frappe.db.get_value(campaign_for, recipient, "email_id") + if not email_id: + frappe.log_error( + title=_("Email Campaign Error"), + message=_("No email found for {0} {1}").format(campaign_for, recipient), ) + return + recipient_list = [email_id] + + if not recipient_list: + frappe.log_error( + title=_("Email Campaign Error"), + message=_("No recipients found for campaign {0}").format(campaign_name), + ) + return + + # Get email template and sender + email_template = frappe.get_cached_doc("Email Template", entry.get("email_template")) + sender = frappe.db.get_value("User", sender_user, "email") if sender_user else None + + # Build context for template rendering + if campaign_for != "Email Group": + context = {"doc": frappe.get_doc(campaign_for, recipient)} + else: + # For email groups, use the email group document as context + context = {"doc": frappe.get_doc("Email Group", recipient)} + + # Render template + subject = frappe.render_template(email_template.get("subject"), context) + content = frappe.render_template(email_template.response_, context) + + try: + comm = make( + doctype="Email Campaign", + name=campaign_name, + subject=subject, + content=content, + sender=sender, + recipients=recipient_list, + communication_medium="Email", + sent_or_received="Sent", + send_email=False, + email_template=email_template.name, ) - email_template = frappe.get_doc("Email Template", entry.get("email_template")) - sender = frappe.db.get_value("User", email_campaign.get("sender"), "email") - context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)} - # send mail and link communication to document - comm = make( - doctype="Email Campaign", - name=email_campaign.name, - subject=frappe.render_template(email_template.get("subject"), context), - content=frappe.render_template(email_template.response_, context), - sender=sender, - bcc=recipient_list, - communication_medium="Email", - sent_or_received="Sent", - send_email=True, - email_template=email_template.name, - ) + frappe.sendmail( + recipients=recipient_list, + subject=subject, + content=content, + sender=sender, + communication=comm["name"], + queue_separately=True, + ) + except Exception: + frappe.log_error(title="Email Campaign Failed.") + return comm @@ -140,7 +210,12 @@ def unsubscribe_recipient(unsubscribe, method): # called through hooks to update email campaign status daily def set_email_campaign_status(): - email_campaigns = frappe.get_all("Email Campaign", filters={"status": ("!=", "Unsubscribed")}) - for entry in email_campaigns: - email_campaign = frappe.get_doc("Email Campaign", entry.name) + email_campaigns = frappe.get_all( + "Email Campaign", + filters={"status": ("!=", "Unsubscribed")}, + pluck="name", + ) + + for name in email_campaigns: + email_campaign = frappe.get_doc("Email Campaign", name) email_campaign.update_status()