mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-12 17:23:38 +00:00
fix: email campaign timeout issue (#51994)
* fix: email campaign timeout issue
* refactor: email campaign backend logic
* refactor: use sendmail instead of manually batching
(cherry picked from commit 22123dd955)
Co-authored-by: Pratik Badhe <badhepd@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user