Skip to content

Instantly share code, notes, and snippets.

@karavan
Last active April 16, 2025 10:19
Show Gist options
  • Select an option

  • Save karavan/f6191646b0ee3c59df5f7ef03cfeba5e to your computer and use it in GitHub Desktop.

Select an option

Save karavan/f6191646b0ee3c59df5f7ef03cfeba5e to your computer and use it in GitHub Desktop.
smtp debug
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import asyncio
import os.path
import aiofiles
import aiocsv
import json
import sys
import aiosqlite
import logging
from aiosmtplib import SMTP
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from datetime import datetime
from jinja2 import Template
import asyncclick as click
REQUESTS_CA_BUNDLE = "/Storage/Virtualenv/smtp-client/team.crt"
# if sys.platform == 'win32':
# loop = asyncio.get_event_loop()
# if not loop.is_running() and not isinstance(loop, asyncio.ProactorEventLoop):
# loop = asyncio.ProactorEventLoop()
# asyncio.set_event_loop(loop)
class Config:
"""Структура конфига для всех доступных команд."""
def __init__(self,
smtp_host,
smtp_port,
smtp_user,
smtp_password,
from_addr,
mail_subject):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
self.smtp_user = smtp_user
self.smtp_password = smtp_password
self.from_addr = from_addr
self.mail_subject = mail_subject
@staticmethod
async def load(file_path: str):
"""Загрузка конфигурации из файла.
Возвращает заполненную конфигурацию.
Параметры:
file_path - полный путь к файлу конфигурации
"""
async with aiofiles.open(file_path, mode="r", encoding="utf-8") as config_file:
conf_str = await config_file.read()
conf = json.loads(conf_str)
return Config(**conf)
class Context:
def __init__(self):
self.logger = None
self.config = None
async def with_smtp(self, send_email_func):
smtp = SMTP(
hostname=self.config.smtp_host,
port=self.config.smtp_port
)
await smtp.connect(validate_certs=False)
await smtp.starttls()
await smtp.login(
username=self.config.smtp_user,
password=self.config.smtp_password
)
await send_email_func(self, smtp)
await smtp.quit()
class TicketsDB:
def __init__(self, conn: aiosqlite.Connection):
self.conn = conn
async def init(self):
create_schema = """create table if not exists tickets
(
number text primary key,
email text,
order_id text,
result text
)"""
await self.conn.execute(create_schema)
async def upsert_ticket(self, email, order_id, result):
query = "insert or replace into tickets(email, order_id, result) values (?, ?, ?)"
await self.conn.execute(query, (email, order_id, result))
async def is_ticket_success(self, number):
query = "select result from tickets where number = :number"
cur: aiosqlite.Cursor = await self.conn.cursor()
await cur.execute(query, {"number":number})
result = await cur.fetchone()
await cur.close()
return result is not None and result[0] == "success"
@click.group()
@click.pass_context
@click.option("--conf", "-c", "conf", type=str, default="config.json", help="Path JSON config file.")
async def cli(ctx: click.core.Context, conf: str):
out_file_name = datetime.now().strftime("logs/%Y-%m-%d__%H-%M-%S") + "_out.txt"
logger = logging.getLogger("asyncio")
fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
stream_handler = logging.StreamHandler(stream=sys.stdout)
stream_handler.setFormatter(fmt)
logger.addHandler(stream_handler)
file_handler = logging.FileHandler(filename=out_file_name)
file_handler.setFormatter(fmt)
logger.addHandler(file_handler)
logger.setLevel(logging.INFO)
ctx.obj.logger = logger
ctx.obj.config = await Config.load(conf)
@cli.command()
@click.pass_context
@click.option("--recipients", "-r", "recipients_path", type=str, default="emails.csv", help="Path to recipients file.")
@click.option("--msg", "-m", "msg_path", type=str, default="template.html", help="Path to message template body.")
async def send_tickets(ctx: click.core.Context, recipients_path: str, msg_path: str):
async def send_email_func(ctx: Context, smtp: SMTP):
async with aiofiles.open(msg_path, mode="r", encoding="utf-8") as template_file:
msg_body = await template_file.read()
tmpl = Template(msg_body)
async with aiofiles.open(recipients_path, mode="r", encoding="utf-8") as csv_file:
recipients = aiocsv.AsyncDictReader(csv_file)
async for r in recipients:
email = r["email"].strip()
bcc_email = r["bcc_email"].strip()
link = r['link'].strip()
text_html = r['text_html']
msg = MIMEMultipart("html")
msg["Subject"] = ctx.config.mail_subject
msg["From"] = ctx.config.from_addr
msg["To"] = email
msg["Bcc"] = bcc_email
body = tmpl.render({"delivery_link": link, "orders_comp": text_html})
msg.attach(MIMEText(body, "html"))
log_msg = f"{email}: "
try:
await smtp.send_message(msg)
ctx.logger.info(log_msg + "success")
except Exception as e:
ctx.logger.error(f"{log_msg}{e}")
await ctx.obj.with_smtp(send_email_func)
if __name__ == '__main__':
cli(obj=Context(), _anyio_backend="asyncio")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment