mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-13 01:34:10 +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>
This commit is contained in:
@@ -38,18 +38,18 @@ class EmailCampaign(Document):
|
|||||||
def set_date(self):
|
def set_date(self):
|
||||||
if getdate(self.start_date) < getdate(today()):
|
if getdate(self.start_date) < getdate(today()):
|
||||||
frappe.throw(_("Start Date cannot be before the current date"))
|
frappe.throw(_("Start Date cannot be before the current date"))
|
||||||
|
|
||||||
# set the end date as start date + max(send after days) in campaign schedule
|
# set the end date as start date + max(send after days) in campaign schedule
|
||||||
send_after_days = []
|
campaign = frappe.get_cached_doc("Campaign", self.campaign_name)
|
||||||
campaign = frappe.get_doc("Campaign", self.campaign_name)
|
send_after_days = [entry.send_after_days for entry in campaign.get("campaign_schedules")]
|
||||||
for entry in campaign.get("campaign_schedules"):
|
|
||||||
send_after_days.append(entry.send_after_days)
|
if not send_after_days:
|
||||||
try:
|
|
||||||
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
|
|
||||||
except ValueError:
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name)
|
_("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):
|
def validate_lead(self):
|
||||||
lead_email_id = frappe.db.get_value("Lead", self.recipient, "email_id")
|
lead_email_id = frappe.db.get_value("Lead", self.recipient, "email_id")
|
||||||
if not lead_email_id:
|
if not lead_email_id:
|
||||||
@@ -77,58 +77,128 @@ class EmailCampaign(Document):
|
|||||||
start_date = getdate(self.start_date)
|
start_date = getdate(self.start_date)
|
||||||
end_date = getdate(self.end_date)
|
end_date = getdate(self.end_date)
|
||||||
today_date = getdate(today())
|
today_date = getdate(today())
|
||||||
|
|
||||||
if start_date > today_date:
|
if start_date > today_date:
|
||||||
self.db_set("status", "Scheduled", update_modified=False)
|
new_status = "Scheduled"
|
||||||
elif end_date >= today_date:
|
elif end_date >= today_date:
|
||||||
self.db_set("status", "In Progress", update_modified=False)
|
new_status = "In Progress"
|
||||||
elif end_date < today_date:
|
else:
|
||||||
self.db_set("status", "Completed", update_modified=False)
|
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
|
# called through hooks to send campaign mails to leads
|
||||||
def send_email_to_leads_or_contacts():
|
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_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)
|
if not email_campaigns:
|
||||||
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
|
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"):
|
for entry in campaign.get("campaign_schedules"):
|
||||||
scheduled_date = add_days(email_campaign.get("start_date"), entry.get("send_after_days"))
|
try:
|
||||||
if scheduled_date == getdate(today()):
|
scheduled_date = add_days(getdate(email_campaign.start_date), entry.get("send_after_days"))
|
||||||
send_mail(entry, email_campaign)
|
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):
|
def send_mail(entry, email_campaign):
|
||||||
recipient_list = []
|
campaign_for = email_campaign.get("email_campaign_for")
|
||||||
if email_campaign.email_campaign_for == "Email Group":
|
recipient = email_campaign.get("recipient")
|
||||||
for member in frappe.db.get_list(
|
sender_user = email_campaign.get("sender")
|
||||||
"Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]
|
campaign_name = email_campaign.get("name")
|
||||||
):
|
|
||||||
recipient_list.append(member["email"])
|
# 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:
|
else:
|
||||||
recipient_list.append(
|
email_id = frappe.db.get_value(campaign_for, recipient, "email_id")
|
||||||
frappe.db.get_value(
|
if not email_id:
|
||||||
email_campaign.email_campaign_for, email_campaign.get("recipient"), "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"))
|
frappe.sendmail(
|
||||||
sender = frappe.db.get_value("User", email_campaign.get("sender"), "email")
|
recipients=recipient_list,
|
||||||
context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
|
subject=subject,
|
||||||
# send mail and link communication to document
|
content=content,
|
||||||
comm = make(
|
sender=sender,
|
||||||
doctype="Email Campaign",
|
communication=comm["name"],
|
||||||
name=email_campaign.name,
|
queue_separately=True,
|
||||||
subject=frappe.render_template(email_template.get("subject"), context),
|
)
|
||||||
content=frappe.render_template(email_template.response_, context),
|
except Exception:
|
||||||
sender=sender,
|
frappe.log_error(title="Email Campaign Failed.")
|
||||||
bcc=recipient_list,
|
|
||||||
communication_medium="Email",
|
|
||||||
sent_or_received="Sent",
|
|
||||||
send_email=True,
|
|
||||||
email_template=email_template.name,
|
|
||||||
)
|
|
||||||
return comm
|
return comm
|
||||||
|
|
||||||
|
|
||||||
@@ -140,7 +210,12 @@ def unsubscribe_recipient(unsubscribe, method):
|
|||||||
|
|
||||||
# called through hooks to update email campaign status daily
|
# called through hooks to update email campaign status daily
|
||||||
def set_email_campaign_status():
|
def set_email_campaign_status():
|
||||||
email_campaigns = frappe.get_all("Email Campaign", filters={"status": ("!=", "Unsubscribed")})
|
email_campaigns = frappe.get_all(
|
||||||
for entry in email_campaigns:
|
"Email Campaign",
|
||||||
email_campaign = frappe.get_doc("Email Campaign", entry.name)
|
filters={"status": ("!=", "Unsubscribed")},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
for name in email_campaigns:
|
||||||
|
email_campaign = frappe.get_doc("Email Campaign", name)
|
||||||
email_campaign.update_status()
|
email_campaign.update_status()
|
||||||
|
|||||||
Reference in New Issue
Block a user