Untitled
unknown
plain_text
10 months ago
14 kB
5
Indexable
import time
import pandas as pd
import smtplib
import tkinter as tk
import os
import threading
import re
import subprocess
import queue
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from docx import Document
from tkinter import filedialog
from tkinter import messagebox
from dotenv import load_dotenv
from email.mime.base import MIMEBase
from email import encoders
def convert_docx_to_html(docx_file_path):
html_file_path = docx_file_path.replace(".docx", ".html")
try:
# Run the `mammoth` command to convert .docx to .html
subprocess.run(["mammoth", docx_file_path, html_file_path], check=True)
with open(html_file_path, 'r', encoding='utf-8') as file:
html_content = file.read()
return html_content
except subprocess.CalledProcessError as e:
raise Exception(f"Error converting DOCX to HTML: {e}")
def extract_html_source_code(html_file_path):
with open(html_file_path, 'r', encoding='utf-8') as file:
html_content = file.read()
return html_content
def email_to_env_var(email):
return email.replace("@", "_").replace(".", "_")
def get_sender_credentials(sender_email):
sender_env_var = email_to_env_var(sender_email)
sender_email = os.getenv(f"{sender_env_var}_EMAIL")
sender_password = os.getenv(f"{sender_env_var}_PASSWORD")
return sender_email, sender_password
def dataframe_to_styled_html(df, merge_first_row=False):
table_style = (
"border-collapse: collapse; width: 65%; font-family: Arial, sans-serif; "
"font-size: 14px; text-align: center;"
)
header_style = (
"background-color: #6A0DAD; color: white; padding: 10px; border: 1px solid black;"
)
cell_style = "padding: 10px; border: 1px solid black;"
row_alt_style = "background-color: #f2f2f2;"
html_table = f'<table style="{table_style}">'
if merge_first_row:
html_table += f'<tr><td colspan="{len(df.columns)}" style="{header_style}">{df.columns[0]}</td></tr>'
else:
html_table += "<tr>"
for col in df.columns:
html_table += f'<th style="{header_style}">{col}</th>'
html_table += "</tr>"
for i, row in df.iterrows():
row_style = row_alt_style if i % 2 == 1 else ""
html_table += f'<tr style="{row_style}">'
for cell in row:
if pd.isna(cell):
cell_content = ""
else:
if isinstance(cell, float):
cell_content = f"{cell:.2%}" if 0 <= cell <= 1 else cell
else:
cell_content = str(cell) # Ensure cell content is always a string
html_table += f'<td style="{cell_style}">{cell_content}</td>'
html_table += "</tr>"
html_table += "</table>"
return html_table
def send_bulk_emails_with_text_and_tables(
smtp_server,
smtp_port,
email_list_file,
table1_file,
table2_file,
table3_file,
table4_file,
table5_file,
log_queue,
log_file
):
log_queue.put("The email-sending process has started. Please wait...")
log_serial_number = 1
with open(log_file, 'a') as log_file_obj:
try:
email_data = pd.read_excel(email_list_file)
if "Email" not in email_data.columns or "Salutations" not in email_data.columns or "CC" not in email_data.columns or "From" not in email_data.columns or "File Name" not in email_data.columns or "Sender Name" not in email_data.columns or "Signature File" not in email_data.columns:
log_queue.put("The email list file must contain columns: 'Email', 'Salutations', 'CC', 'From', 'File Name', 'Sender Name', and 'Signature File'.")
return
email_details = email_data[["Email", "Salutations", "CC", "From", "File Name", "Sender Name", "Attachments", "Signature File"]]
tables = [
(table1_file, "{{ TABLE_1 }}"),
(table2_file, "{{ TABLE_2 }}"),
(table3_file, "{{ TABLE_3 }}"),
(table4_file, "{{ TABLE_4 }}"),
(table5_file, "{{ TABLE_5 }}")
]
# Read all tables as dataframes if they are valid files
tables_html = {}
for table_file, placeholder in tables:
if table_file and os.path.isfile(table_file):
try:
table_df = pd.read_excel(table_file)
tables_html[placeholder] = dataframe_to_styled_html(table_df)
except Exception as e:
log_entry = f"Error reading table from {table_file}: {e}"
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
for _, row in email_details.iterrows():
try:
recipients = row["Email"].split(",")
salutation = row["Salutations"]
cc_list = row["CC"].split(",") if pd.notna(row["CC"]) else []
from_email = row["From"]
text_file = row["File Name"]
sender_name = row["Sender Name"]
signature_file = row["Signature File"]
signature_html = ""
if pd.notna(signature_file) and os.path.isfile(signature_file):
signature_html = convert_docx_to_html(signature_file)
attachment_file = row.get("Attachments", None)
if not os.path.isfile(text_file):
log_entry = f"{log_serial_number}. The file {text_file} does not exist for {from_email}."
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
continue
# html_file_path = convert_docx_to_html(text_file)
path = "Reports/mfReportsBody.html"
email_text = extract_html_source_code(path)
# Replace placeholders for tables dynamically
for placeholder, table_html in tables_html.items():
if placeholder in email_text:
email_text = email_text.replace(placeholder, table_html)
else:
log_queue.put(f"Placeholder {placeholder} not found in the text.")
# Replace the {{ Sender_Name }} and {{ Signature }} placeholders
email_text = email_text.replace("{{ Sender_Name }}", sender_name)
email_text = email_text.replace("{{ Signature }}", signature_html)
email_body_html = f"""
<html>
<body>
{email_text}
</body>
</html>
"""
# Fetch sender email and password based on the 'From' column
sender_email, sender_password = get_sender_credentials(from_email)
if not sender_email or not sender_password:
log_entry = f"{log_serial_number}. Missing credentials for sender email {from_email}."
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
continue
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(sender_email, sender_password)
personalized_text = email_body_html.replace("{{ Salutations }}", salutation)
msg = MIMEMultipart()
msg["From"] = sender_email
msg["To"] = ", ".join(recipients)
msg["Cc"] = ", ".join(cc_list)
msg["Subject"] = "Mutual Fund Investment Snapshot - Dated as of Dec 31, 2024"
msg.attach(MIMEText(personalized_text, "html"))
# Handle attachments (if provided)
if attachment_file and os.path.isfile(attachment_file):
with open(attachment_file, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename={os.path.basename(attachment_file)}"
)
msg.attach(part)
log_entry = f"Attachment {os.path.basename(attachment_file)} added."
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
all_recipients = recipients + cc_list
server.sendmail(sender_email, all_recipients, msg.as_string())
log_entry = f"{log_serial_number}. Email sent successfully from {sender_email} to {', '.join(recipients)}, CC: {', '.join(cc_list)}"
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
except Exception as e:
log_entry = f"{log_serial_number}. Failed to send email from {from_email} to {', '.join(recipients)}: {e}"
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
log_serial_number += 1
except Exception as e:
log_entry = f"Failed to connect to the SMTP server: {e}"
log_queue.put(log_entry)
log_file_obj.write(log_entry + "\n")
log_queue.put("The email-sending process has completed successfully.")
# to show logs in the ui box
def update_log_ui(log_queue, log_text_widget):
try:
while not log_queue.empty():
log_entry = log_queue.get_nowait()
log_text_widget.insert(tk.END, log_entry + "\n")
log_text_widget.yview(tk.END)
except queue.Empty:
pass
finally:
# Schedule the next update
root.after(100, update_log_ui, log_queue, log_text_widget)
# threading is used to send emails slowly so it doesnt go into spam
def send_email_threaded():
smtp_server = "smtp.gmail.com"
smtp_port = 587
email_list_file = email_list_entry.get()
table_files = [
table1_entry.get(),
table2_entry.get(),
table3_entry.get(),
table4_entry.get(),
table5_entry.get()
]
log_file = "email_log.txt"
if not email_list_file:
messagebox.showerror("Missing Information", "Please provide the email list file.")
else:
log_queue = queue.Queue()
email_thread = threading.Thread(
target=send_bulk_emails_with_text_and_tables,
args=(
smtp_server,
smtp_port,
email_list_file,
*table_files, # Pass the table files dynamically
log_queue,
log_file,
),
)
email_thread.start()
update_log_ui(log_queue, log_text_widget)
#upload file from ui
def browse_file(entry):
file_path = filedialog.askopenfilename(
title="Select a file",
filetypes=[("Excel files", ".xlsx")]
)
if file_path:
entry.delete(0, tk.END)
entry.insert(0, file_path)
load_dotenv()
# UI Setup
# UI Setup
root = tk.Tk()
root.title("Pygmalion Wealth")
tk.Label(root, text="Sender Email:").grid(row=0, column=0, padx=10, pady=5)
sender_email_entry = tk.Entry(root, width=40)
sender_email_entry.grid(row=0, column=1, padx=10, pady=5)
tk.Label(root, text="Email List File:").grid(row=2, column=0, padx=10, pady=5)
email_list_entry = tk.Entry(root, width=40)
email_list_entry.grid(row=2, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(email_list_entry)).grid(row=2, column=2, padx=10, pady=5)
tk.Label(root, text="Table 1 File (Optional):").grid(row=3, column=0, padx=10, pady=5)
table1_entry = tk.Entry(root, width=40)
table1_entry.grid(row=3, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table1_entry)).grid(row=3, column=2, padx=10, pady=5)
tk.Label(root, text="Table 2 File (Optional):").grid(row=4, column=0, padx=10, pady=5)
table2_entry = tk.Entry(root, width=40)
table2_entry.grid(row=4, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table2_entry)).grid(row=4, column=2, padx=10, pady=5)
tk.Label(root, text="Table 3 File (Optional):").grid(row=5, column=0, padx=10, pady=5)
table3_entry = tk.Entry(root, width=40)
table3_entry.grid(row=5, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table3_entry)).grid(row=5, column=2, padx=10, pady=5)
tk.Label(root, text="Table 4 File (Optional):").grid(row=6, column=0, padx=10, pady=5)
table4_entry = tk.Entry(root, width=40)
table4_entry.grid(row=6, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table4_entry)).grid(row=6, column=2, padx=10, pady=5)
tk.Label(root, text="Table 5 File (Optional):").grid(row=7, column=0, padx=10, pady=5)
table5_entry = tk.Entry(root, width=40)
table5_entry.grid(row=7, column=1, padx=10, pady=5)
tk.Button(root, text="Browse", command=lambda: browse_file(table5_entry)).grid(row=7, column=2, padx=10, pady=5)
log_text_widget = tk.Text(root, height=15, width=80)
log_text_widget.grid(row=8, column=0, columnspan=3, padx=10, pady=10)
tk.Button(root, text="Send Emails", command=send_email_threaded).grid(row=9, column=1, padx=10, pady=5)
root.mainloop()
Editor is loading...
Leave a Comment