|
| 1 | +import smtplib |
| 2 | +from email.mime.text import MIMEText |
| 3 | +from email.mime.multipart import MIMEMultipart |
| 4 | +from email.mime.base import MIMEBase |
| 5 | +from email import encoders |
| 6 | +import pandas as pd |
| 7 | +import streamlit as st |
| 8 | +import time |
| 9 | +import os |
| 10 | + |
| 11 | +# ---------------- Streamlit Page Config ---------------- |
| 12 | +st.set_page_config(page_title="📧 Bulk Email Dashboard", page_icon="📨", layout="centered") |
| 13 | +st.title("📨 Bulk Email Sender") |
| 14 | +st.caption("Send personalized bulk emails with live logs, attachments, and custom names") |
| 15 | + |
| 16 | +st.markdown("---") |
| 17 | + |
| 18 | +# ---------------- Email Login Section ---------------- |
| 19 | +with st.expander("🔑 Gmail Login Details"): |
| 20 | + sender_name = st.text_input("Sender Name (appears in inbox)", placeholder="Your Name") |
| 21 | + sender_email = st.text_input("Your Gmail Address", placeholder="youremail@gmail.com") |
| 22 | + sender_password = st.text_input("App Password", type="password", placeholder="16-character Gmail App Password") |
| 23 | + |
| 24 | +# ---------------- CSV Upload ---------------- |
| 25 | +uploaded_file = st.file_uploader("📂 Upload CSV (columns: Name, Email)", type=["csv"]) |
| 26 | + |
| 27 | +st.markdown(""" |
| 28 | +**Example CSV format:** |
| 29 | +| Name | Email | |
| 30 | +|------|--------------------| |
| 31 | +| Rahul Pandey | rahul@example.com | |
| 32 | +| Riya Sharma | riya@example.com | |
| 33 | +""") |
| 34 | + |
| 35 | +# ---------------- Manual Email Entry ---------------- |
| 36 | +with st.expander("✏️ Add Optional Emails Manually"): |
| 37 | + manual_emails = st.text_area( |
| 38 | + "Enter additional emails manually (format: Name:Email, separated by commas)", |
| 39 | + placeholder="Rahul Pandey:rahul@gmail.com, Riya Sharma:riya@gmail.com" |
| 40 | + ) |
| 41 | + |
| 42 | +# ---------------- Email Content ---------------- |
| 43 | +subject = st.text_input("✉️ Email Subject", placeholder="Hello {Name}, here’s your update!") |
| 44 | +message_template = st.text_area( |
| 45 | + "📝 Email Body (you can use placeholders like {Name})", |
| 46 | + height=150, |
| 47 | + placeholder="Dear {Name},\n\nHope you're doing well!\n\nBest regards,\nYour Name" |
| 48 | +) |
| 49 | + |
| 50 | +# ---------------- File Attachment ---------------- |
| 51 | +attachment = st.file_uploader("📎 Optional: Attach a file (PDF, Image, or DOCX)", type=["pdf", "jpg", "jpeg", "png", "docx"]) |
| 52 | + |
| 53 | +# ---------------- Email Preview ---------------- |
| 54 | +if st.button("👁️ Preview Email"): |
| 55 | + if uploaded_file: |
| 56 | + df_preview = pd.read_csv(uploaded_file) |
| 57 | + if "Name" in df_preview.columns and len(df_preview) > 0: |
| 58 | + name_sample = df_preview["Name"][0] |
| 59 | + st.markdown("### 🧾 Preview for first recipient:") |
| 60 | + st.info(f"**Subject:** {subject.format(Name=name_sample)}\n\n{message_template.format(Name=name_sample)}") |
| 61 | + else: |
| 62 | + st.warning("CSV must have a 'Name' column with at least one entry.") |
| 63 | + else: |
| 64 | + st.warning("Please upload a CSV first.") |
| 65 | + |
| 66 | +# ---------------- Send Button ---------------- |
| 67 | +if st.button("🚀 Send Emails"): |
| 68 | + if not sender_email or not sender_password: |
| 69 | + st.error("Please enter your Gmail and App Password.") |
| 70 | + elif uploaded_file is None and not manual_emails: |
| 71 | + st.error("Please upload a CSV file or enter emails manually.") |
| 72 | + else: |
| 73 | + try: |
| 74 | + recipients = [] |
| 75 | + |
| 76 | + # Load from CSV if available |
| 77 | + if uploaded_file is not None: |
| 78 | + df = pd.read_csv(uploaded_file) |
| 79 | + if "Email" not in df.columns or "Name" not in df.columns: |
| 80 | + st.error("CSV must contain columns 'Name' and 'Email'.") |
| 81 | + st.stop() |
| 82 | + for _, row in df.iterrows(): |
| 83 | + recipients.append({"Name": row["Name"], "Email": row["Email"]}) |
| 84 | + |
| 85 | + # Add manual emails (support Name:Email or just Email) |
| 86 | + if manual_emails: |
| 87 | + for entry in [e.strip() for e in manual_emails.split(",") if e.strip()]: |
| 88 | + if ":" in entry: |
| 89 | + name, email = entry.split(":", 1) |
| 90 | + recipients.append({"Name": name.strip(), "Email": email.strip()}) |
| 91 | + else: |
| 92 | + recipients.append({"Name": "Friend", "Email": entry.strip()}) |
| 93 | + |
| 94 | + # Connect to Gmail |
| 95 | + server = smtplib.SMTP("smtp.gmail.com", 587) |
| 96 | + server.starttls() |
| 97 | + server.login(sender_email, sender_password) |
| 98 | + st.success("✅ Logged in successfully!") |
| 99 | + |
| 100 | + # UI Setup: progress bar + live logs |
| 101 | + progress = st.progress(0) |
| 102 | + status = st.empty() |
| 103 | + log_box = st.container() |
| 104 | + total = len(recipients) |
| 105 | + success_count = 0 |
| 106 | + |
| 107 | + # ---------------- Sending Loop ---------------- |
| 108 | + for i, recipient in enumerate(recipients): |
| 109 | + recipient_email = recipient["Email"] |
| 110 | + recipient_name = recipient["Name"] |
| 111 | + |
| 112 | + msg = MIMEMultipart() |
| 113 | + msg["From"] = f"{sender_name} <{sender_email}>" |
| 114 | + msg["To"] = recipient_email |
| 115 | + msg["Subject"] = subject.format(Name=recipient_name) |
| 116 | + |
| 117 | + # Message body |
| 118 | + body = message_template.format(Name=recipient_name) |
| 119 | + msg.attach(MIMEText(body, "plain")) |
| 120 | + |
| 121 | + # Add attachment if uploaded |
| 122 | + if attachment is not None: |
| 123 | + part = MIMEBase("application", "octet-stream") |
| 124 | + part.set_payload(attachment.getvalue()) |
| 125 | + encoders.encode_base64(part) |
| 126 | + part.add_header( |
| 127 | + "Content-Disposition", |
| 128 | + f"attachment; filename={attachment.name}" |
| 129 | + ) |
| 130 | + msg.attach(part) |
| 131 | + |
| 132 | + # Send email and log live |
| 133 | + try: |
| 134 | + server.send_message(msg) |
| 135 | + success_count += 1 |
| 136 | + with log_box: |
| 137 | + st.markdown(f"✅ **Sent to {recipient_name}** ({recipient_email})") |
| 138 | + except Exception as e: |
| 139 | + with log_box: |
| 140 | + st.markdown(f"⚠️ **Failed to send to {recipient_email}:** {e}") |
| 141 | + |
| 142 | + # Update progress bar |
| 143 | + progress.progress((i + 1) / total) |
| 144 | + status.text(f"📨 Sending email {i+1}/{total}...") |
| 145 | + |
| 146 | + time.sleep(0.5) |
| 147 | + |
| 148 | + server.quit() |
| 149 | + st.success(f"✅ Successfully sent {success_count}/{total} emails!") |
| 150 | + st.balloons() |
| 151 | + |
| 152 | + except Exception as e: |
| 153 | + st.error(f"❌ Error: {e}") |
0 commit comments