YSTONe
자동 메일링 RPA 만들기 본문
RPA 자동 메일링을 만든 계기...
1. 내가 담당하는 고객이 수백개가 넘는다.
2. 각 고객에게 "고객 별 개별 실적"을 전송해야 한다.
3. 원래 같으면 고객 별로 엑셀 필터걸고 표 복사, 붙여넣기, 고객 이메일 찾아서 수신&CC 넣기
한 개 메일을 보내는데 3분이 걸려도, 100개의 메일을 발송하려면 5 시간이 소요된다.
메일 body의 내용이 거의 동일하고, 고객 별로 실적만 고객에 맞게 표가 들어간다면, 해결될 수 있다.
고려 사항
1. 동료들도 사용할 수 있도록 WEB 형태로 개발
2. 엑셀 붙여넣기가 필요함
그래서 나온 결과물
디렉토리 구조
- auto_amil
> app.py
-- doc
> seller_info.txt
--templates
> index.html
> customer_info.html
필요한 라이브러리
from flask import Flask, render_template, request
from flask_caching import Cache
import os
import pandas as pd
import datetime
import smtplib
from email.mime.multipart import MIMEMultipart
from email.header import Header
고객정보를 메모장으로 기록하고, 이를 IMPORT하기 위한 함수
## 고객 리스트를 불러오는 함수
def open_xls(csv):
seller_list = pd.read_csv(".//doc//{}.txt".format(csv), sep="\t")
return seller_list
판다스 TO_HTML 함수를 좀 더 이쁘게 꾸며주기 위한 함수
## pandas표를 조금 더 이쁘게 바꾸는 함수
def df_to_html(df):
#body = df.to_html()
print("html 변환 시작")
body = df.to_html(justify='center').replace('<td>', '<td align="center">')
df_html = body.replace('<table border="3" class="dataframe">', '<table border="3" class="dataframe" bgcolor=black cellpadding="5" cellspacing=5><tr><td><table border="3" class="dataframe" bgcolor=black>')
df_html = df_html.replace('</table>', '</table> </td></tr></table>')
df_html = df_html.replace('<td>','<td padding = 30px bgcolor=white>')
df_html = df_html.replace('<th>','<th bgcolor=#e5e5e5>')
return df_html
고객 리스트를 루프돌며 메일을 발송하는 함수
본인에 맞게 수정 필요한 부분 :
1. cc_mail_list : 고정으로 CC걸릴 동료들의 이메일 주소를 ,(콤마)구분으로 작성해주세요
2. SMTP : 본인이 사용하는 SMTP 도메인과 포트 작성해주세요.
### WEB으로부터 정보를 받으면 실행되는 메일 발송 함수
def shoot_mail(user_id,pw,adress,start,end,subject):
global df
fromMail = adress
pw = pw
mail_name = user_id
cc_mail_list = "고정으로 참조할 이메일 주소를 입력하세요, 여러개일 경우 ,구분으로 작성(띄어쓰기 금지)"
## 예시 : cc_mail_list = "aa@aa.com,bb@bb.com,cc@cc.com"
smtp_dom = "SMTP 도메인을 적어주세요, 예시 : smtp@yahoo.com"
smtp_port = "SMTP 포트를 적어주세요(int로 작성) 예시 : 587"
header ="""<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
"""
foot = '</html>'
msg_desc = end
# df = open_xls("order_info")
#print(df)
print('file load complete')
df_data = df
## print(df_data)
seller_list = open_xls("seller_info")
print('seller imforamtion load complete')
key_in_res = pd.merge(left = df_data, right = seller_list, left_on = '고객', right_on = 'seller')
## print(key_in_res)
key_val = key_in_res['seller'].unique()
for i in key_val:
try:
col = key_in_res.columns.to_list()
col.remove("seller")
col.remove("mail")
print(col)
df_res = key_in_res.loc[key_in_res['seller']==i,:]
# html_res =df_to_html(df_res.loc[:,col])
html_table =(df_res.loc[:,col]).to_html(classes="table", escape=False)
### test
df_res = df_res.fillna("-")
df_res = df_res.astype("string")
print("df_res의 타입", type(df_res))
for cl in df_res.columns:
df_res[cl] = df_res[cl].apply(lambda x: x.rstrip('.0') if x.endswith('.0') else x)
df_res[cl] = df_res[cl].replace('', '-', regex=True)
#print(type(df_res))
### test
## orign
# df_res = df_res.astype("string")
# df_res = df_res.fillna("-")
## orign
html_res = df_to_html(df_res.loc[:,col])
print("변환 확인")
#seller = i
html_code = f"""
<!DOCTYPE html>
<html>
<head>
<title>Styled Table Example</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2>Styled Table Example</h2>
{html_table}
</div>
</body>
</html>
"""
## print("df_res 결과:", df_res)
msg_header = start + " <br> <br> "
print("메일 본문 완성")
msg_part = MIMEText(msg_header + html_res +" <br> <br> "+msg_desc+foot, 'html', _charset="utf8")
to_mail = df_res['mail'].unique()[0]
print("메일링 동작")
s = smtplib.SMTP(smtp_dom, smtp_port)
s.starttls()
s.login(mail_name+"도메인주소 뒷 부분(예시 : @bb.com)", pw)
msg = MIMEMultipart('alternative')
msg['Subject'] = "[{}]".format(i)+subject
msg['From'] = adress
msg['To'] = to_mail
msg['CC'] = cc_mail_list
msg.attach(msg_part)
print("수신인 지정")
s.sendmail(msg["From"], msg["To"].split(",") + cc_mail_list.split(","),msg.as_string())
print("메일 발송 완료")
s.quit()
except:
pass
print('sending emails done')
FLASK로 WEB 구현
app = Flask(__name__)
cache = Cache(app)
df = []
ITEMS_PER_PAGE = 7 # 한 페이지에 표시할 아이템 개수
uploaded_data = []
headers = [] # 컬럼 정보
@app.route('/', methods=['GET', 'POST'])
@cache.cached(timeout=60) # 캐시를 사용하며 60초 동안 유지
def index():
global headers # 함수 내에서 전역 변수를 수정하기 위해 선언
global df
if request.method == 'POST':
pasted_data = request.form.get('pasted_data', None)
if pasted_data:
rows = pasted_data.split('\n')
csv_data = [row.split('\t') for row in rows] # 데이터를 탭으로 분리
data_stream = StringIO(pasted_data)
df = pd.read_csv(data_stream, sep= "\t")
#print(df)
headers = csv_data[0] # Assuming first row is header
headers.append('구분') # 발송여부 컬럼 추가
uploaded_data.clear()
for row in csv_data[1:]:
uploaded_data.append(row + ['발송대상']) # Add default value "발송대기"
page = int(request.args.get('page', 1)) if request.args.get('page') else 1 # 페이지 번호를 받아옴 (최초 열었을 때 처리)
start_idx = (page - 1) * ITEMS_PER_PAGE
end_idx = start_idx + ITEMS_PER_PAGE
num_pages = -(-len(uploaded_data) // ITEMS_PER_PAGE) if uploaded_data else 0 # 올림 나눗셈으로 총 페이지 수 계산
return render_template('index.html', headers=headers, data=uploaded_data[start_idx:end_idx], page=page, num_pages=num_pages)
from flask import request, jsonify
@app.route('/send_mail', methods=['POST'])
def send_mail():
user_id = request.form['id']
pw = request.form['pw']
address = request.form['address']
subject = request.form['subject']
start = request.form['start']
end = request.form['end']
shoot_mail(user_id, pw, address, start, end, subject)
return jsonify({"message": "메일 발송 요청을 성공적으로 받았습니다."})
@app.route('/customer_info')
def customer_info():
return render_template('customer_info.html')
if __name__ == '__main__':
app.run(debug=True,host="0.0.0.0", port="9989")
WEB 구현(HTML)
index.html
<!DOCTYPE html>
<html>
<head>
<title>LoIS YSTONe</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="icon" href="{{ url_for('static', filename='logo.ico') }}" type="image/x-icon">
<link rel="shortcut icon" href="{{ url_for('static', filename='logo.ico') }}" type="image/x-icon">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="#">LoIS YSTONe</a>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('customer_info') }}">고객정보</a>
</li>
</ul>
</nav>
<div class="container mt-5">
<h1>AUTO eMAILER</h1>
<form method="POST" action="/" enctype="multipart/form-data">
<div class="form-group">
<label for="pasted_data">엑셀 데이터를 넣어주세요.</label>
<textarea class="form-control" id="pasted_data" name="pasted_data" rows="2"></textarea>
</div>
<button type="submit" class="btn btn-primary">업로드</button>
</form>
<div class="input-group mt-3">
<input type="text" class="form-control" id="subject" placeholder="제목을 작성해주세요.">
</div>
<div class="input-group mt-3">
<input type="text" class="form-control" id = "start" placeholder="시작멘트를 작성해주세요.">
</div>
<div class="table-responsive">
<table class="table mt-3" id = "dataTable">
<thead>
<tr>
{% for header in headers %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
{% for cell in row %}
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="input-group mt-3">
<input type="text" class="form-control" id = "end" placeholder="엔딩멘트를 작성해주세요.">
</div>
<button type="button" class="btn btn-success mt-3" data-toggle="modal" data-target="#sendMailModal">
발송
</button>
<div class="mt-3">
<ul class="pagination">
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="?page={{ page - 1 }}">이전</a>
</li>
{% for page_num in range(1, num_pages+1) %}
<li class="page-item {% if page_num == page %}active{% endif %}">
<a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a>
</li>
{% endfor %}
<li class="page-item {% if page == num_pages %}disabled{% endif %}">
<a class="page-link" href="?page={{ page + 1 }}">다음</a>
</li>
</ul>
</div>
<div class="modal fade" id="sendMailModal" tabindex="-1" role="dialog" aria-labelledby="sendMailModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="sendMailModalLabel">메일 발송 정보 입력</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="mailId">cj world 아이디</label>
<input type="text" class="form-control" id="mailId">
</div>
<div class="form-group">
<label for="mailPw">cj world 패스워드</label>
<input type="password" class="form-control" id="mailPw">
</div>
<div class="form-group">
<label for="mailAddress">본인 이메일 주소</label>
<input type="email" class="form-control" id="mailAddress">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">취소</button>
<button type="button" class="btn btn-primary" id="sendMailButton">발송</button>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.5/xlsx.full.min.js"></script>
<script>
// 발송 버튼 클릭 시 모달 초기화
$('#sendMailModal').on('show.bs.modal', function (event) {
$('#mailId').val('');
$('#mailPw').val('');
$('#mailAddress').val('');
});
// 발송 버튼 클릭 이벤트 처리
$('#sendMailButton').click(function() {
const id = $('#mailId').val();
const pw = $('#mailPw').val();
const address = $('#mailAddress').val();
const subject = $('#subject').val();
const start = $('#start').val();
const end = $('#end').val();
const tableData = [];
$('#dataTable tbody tr').each(function() {
const rowData = [];
$(this).find('td').each(function() {
rowData.push($(this).text());
});
tableData.push(rowData);
});
console.log("id:",id);
console.log("address:",address);
console.log("subject:", subject);
console.log("start:", start);
console.log("end:", end);
console.log(tableData)
// 여기서 shoot_mail 함수 호출 및 필요한 변수 전달
$.ajax({
type: "POST",
url: "/send_mail", // app.py에서 정의한 라우트 주소
data: {
id: id,
pw: pw,
address: address,
subject: subject,
start: start,
end: end
},
success: function(response) {
console.log("서버 응답:", response);
$('#sendMailModal').modal('hide'); // 모달 창 닫기
if (response.message === "메일 발송 요청을 성공적으로 받았습니다.") {
alert("발송 완료");
} else {
alert("발송 실패");
}
},
error: function(xhr, status, error) {
console.error("에러:", error);
alert("발송 실패")
}
});
});
</script>
</body>
</html>
고객 정보 조회, 수정(만드는 중..)
customer_info.html
<!DOCTYPE html>
<html>
<head>
<title>LoIS YSTONe</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="/">LoIS YSTONe</a>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('customer_info') }}">고객정보</a>
</li>
</ul>
</nav>
<div class="container mt-5">
<h1>Seller Info</h1>
</div>
</body>
</html>
'YSTONe_coding > Logistics&RPA' 카테고리의 다른 글
풀필먼트 수요예측 (0) | 2023.10.03 |
---|