Skip to content

Commit 6c9681b

Browse files
fix: email campaign timeout issue (backport #51994) (#52555)
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 22123dd) Co-authored-by: Pratik Badhe <badhepd@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 9519773 commit 6c9681b

1 file changed

Lines changed: 121 additions & 46 deletions

File tree

erpnext/crm/doctype/email_campaign/email_campaign.py

Lines changed: 121 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ def validate(self):
3838
def set_date(self):
3939
if getdate(self.start_date) < getdate(today()):
4040
frappe.throw(_("Start Date cannot be before the current date"))
41+
4142
# set the end date as start date + max(send after days) in campaign schedule
42-
send_after_days = []
43-
campaign = frappe.get_doc("Campaign", self.campaign_name)
44-
for entry in campaign.get("campaign_schedules"):
45-
send_after_days.append(entry.send_after_days)
46-
try:
47-
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
48-
except ValueError:
43+
campaign = frappe.get_cached_doc("Campaign", self.campaign_name)
44+
send_after_days = [entry.send_after_days for entry in campaign.get("campaign_schedules")]
45+
46+
if not send_after_days:
4947
frappe.throw(
5048
_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name)
5149
)
5250

51+
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
52+
5353
def validate_lead(self):
5454
lead_email_id = frappe.db.get_value("Lead", self.recipient, "email_id")
5555
if not lead_email_id:
@@ -77,58 +77,128 @@ def update_status(self):
7777
start_date = getdate(self.start_date)
7878
end_date = getdate(self.end_date)
7979
today_date = getdate(today())
80+
8081
if start_date > today_date:
81-
self.db_set("status", "Scheduled", update_modified=False)
82+
new_status = "Scheduled"
8283
elif end_date >= today_date:
83-
self.db_set("status", "In Progress", update_modified=False)
84-
elif end_date < today_date:
85-
self.db_set("status", "Completed", update_modified=False)
84+
new_status = "In Progress"
85+
else:
86+
new_status = "Completed"
87+
88+
if self.status != new_status:
89+
self.db_set("status", new_status, update_modified=False)
8690

8791

8892
# called through hooks to send campaign mails to leads
8993
def send_email_to_leads_or_contacts():
94+
today_date = getdate(today())
95+
96+
# Get all active email campaigns in a single query
9097
email_campaigns = frappe.get_all(
91-
"Email Campaign", filters={"status": ("not in", ["Unsubscribed", "Completed", "Scheduled"])}
98+
"Email Campaign",
99+
filters={"status": "In Progress"},
100+
fields=["name", "campaign_name", "email_campaign_for", "recipient", "start_date", "sender"],
92101
)
93-
for camp in email_campaigns:
94-
email_campaign = frappe.get_doc("Email Campaign", camp.name)
95-
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
102+
103+
if not email_campaigns:
104+
return
105+
106+
# Process each email campaign
107+
for email_campaign in email_campaigns:
108+
try:
109+
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
110+
except frappe.DoesNotExistError:
111+
frappe.log_error(
112+
title=_("Email Campaign Error"),
113+
message=_("Campaign {0} not found").format(email_campaign.campaign_name),
114+
)
115+
continue
116+
117+
# Find schedules that match today
96118
for entry in campaign.get("campaign_schedules"):
97-
scheduled_date = add_days(email_campaign.get("start_date"), entry.get("send_after_days"))
98-
if scheduled_date == getdate(today()):
99-
send_mail(entry, email_campaign)
119+
try:
120+
scheduled_date = add_days(getdate(email_campaign.start_date), entry.get("send_after_days"))
121+
if scheduled_date == today_date:
122+
send_mail(entry, email_campaign)
123+
except Exception:
124+
frappe.log_error(
125+
title=_("Email Campaign Send Error"),
126+
message=_("Failed to send email for campaign {0} to {1}").format(
127+
email_campaign.name, email_campaign.recipient
128+
),
129+
)
100130

101131

102132
def send_mail(entry, email_campaign):
103-
recipient_list = []
104-
if email_campaign.email_campaign_for == "Email Group":
105-
for member in frappe.db.get_list(
106-
"Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]
107-
):
108-
recipient_list.append(member["email"])
133+
campaign_for = email_campaign.get("email_campaign_for")
134+
recipient = email_campaign.get("recipient")
135+
sender_user = email_campaign.get("sender")
136+
campaign_name = email_campaign.get("name")
137+
138+
# Get recipient emails
139+
if campaign_for == "Email Group":
140+
recipient_list = frappe.get_all(
141+
"Email Group Member",
142+
filters={"email_group": recipient, "unsubscribed": 0},
143+
pluck="email",
144+
)
109145
else:
110-
recipient_list.append(
111-
frappe.db.get_value(
112-
email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id"
146+
email_id = frappe.db.get_value(campaign_for, recipient, "email_id")
147+
if not email_id:
148+
frappe.log_error(
149+
title=_("Email Campaign Error"),
150+
message=_("No email found for {0} {1}").format(campaign_for, recipient),
113151
)
152+
return
153+
recipient_list = [email_id]
154+
155+
if not recipient_list:
156+
frappe.log_error(
157+
title=_("Email Campaign Error"),
158+
message=_("No recipients found for campaign {0}").format(campaign_name),
114159
)
160+
return
161+
162+
# Get email template and sender
163+
email_template = frappe.get_cached_doc("Email Template", entry.get("email_template"))
164+
sender = frappe.db.get_value("User", sender_user, "email") if sender_user else None
165+
166+
# Build context for template rendering
167+
if campaign_for != "Email Group":
168+
context = {"doc": frappe.get_doc(campaign_for, recipient)}
169+
else:
170+
# For email groups, use the email group document as context
171+
context = {"doc": frappe.get_doc("Email Group", recipient)}
172+
173+
# Render template
174+
subject = frappe.render_template(email_template.get("subject"), context)
175+
content = frappe.render_template(email_template.response_, context)
176+
177+
try:
178+
comm = make(
179+
doctype="Email Campaign",
180+
name=campaign_name,
181+
subject=subject,
182+
content=content,
183+
sender=sender,
184+
recipients=recipient_list,
185+
communication_medium="Email",
186+
sent_or_received="Sent",
187+
send_email=False,
188+
email_template=email_template.name,
189+
)
190+
191+
frappe.sendmail(
192+
recipients=recipient_list,
193+
subject=subject,
194+
content=content,
195+
sender=sender,
196+
communication=comm["name"],
197+
queue_separately=True,
198+
)
199+
except Exception:
200+
frappe.log_error(title="Email Campaign Failed.")
115201

116-
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
117-
sender = frappe.db.get_value("User", email_campaign.get("sender"), "email")
118-
context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
119-
# send mail and link communication to document
120-
comm = make(
121-
doctype="Email Campaign",
122-
name=email_campaign.name,
123-
subject=frappe.render_template(email_template.get("subject"), context),
124-
content=frappe.render_template(email_template.response_, context),
125-
sender=sender,
126-
bcc=recipient_list,
127-
communication_medium="Email",
128-
sent_or_received="Sent",
129-
send_email=True,
130-
email_template=email_template.name,
131-
)
132202
return comm
133203

134204

@@ -140,7 +210,12 @@ def unsubscribe_recipient(unsubscribe, method):
140210

141211
# called through hooks to update email campaign status daily
142212
def set_email_campaign_status():
143-
email_campaigns = frappe.get_all("Email Campaign", filters={"status": ("!=", "Unsubscribed")})
144-
for entry in email_campaigns:
145-
email_campaign = frappe.get_doc("Email Campaign", entry.name)
213+
email_campaigns = frappe.get_all(
214+
"Email Campaign",
215+
filters={"status": ("!=", "Unsubscribed")},
216+
pluck="name",
217+
)
218+
219+
for name in email_campaigns:
220+
email_campaign = frappe.get_doc("Email Campaign", name)
146221
email_campaign.update_status()

0 commit comments

Comments
 (0)