이전 내용
[파이썬] FastAPI - 메모 앱 프로젝트 14: 비밀번호 찾기(수정)
이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 11: 회원 탈퇴이전 내용 [
puppy-foot-it.tistory.com
이메일 발송 기능 보완
비밀번호 변경 시 이메일 안내 기능 구현 등
혹시나, 계정 정보가 노출되어 타인이 내 계정의 비밀번호를 변경했을 경우 사용자에게 이메일로 안내하여 비밀번호를 다시 변경할 수 있도록 하는 기능을 추가해 준다.
또한, 모든 이메일 전송 내용에 페이지 링크를 추가하여 이메일에서 바로 페이지로 접속할 수 있도록 보완해 본다.
◆ email_class.py
email_class.py 파일에 비밀번호 변경 시 email을 발송하는 클래스를 생성해 준다.
class EmailServiceSendNewPW:
def send_email(
self,
receiver_email: str,
username: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart("alternative") # <-- 중요! HTML을 포함하려면 "alternative"로 설정
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "비밀번호 변경 안내 이메일"
# HTML 본문 구성
html_body = f"""
<html>
<body>
<p><strong>{username}</strong> 사용자님의 비밀번호가 변경되었습니다.</p>
<p>만약 본인이 변경한 것이 아니라면 즉시 아래 링크를 통해 비밀번호를 변경해 주세요.</p>
<p><a href="http://localhost:8000/change_pw" target="_blank">➡️ 비밀번호 변경하러 가기</a></p>
</body>
</html>
"""
message.attach(MIMEText(html_body, "html")) # HTML 형식으로 첨부
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
print(f"이메일이 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
print(f"이메일 전송 실패: {e}")
★ 이메일 내용에 링크 넣기
이메일 본문에서 클릭 가능한 링크를 만들고 싶다면, 단순한 plain text 형식이 아니라 HTML 형식으로 이메일을 보내야 한다. 따라서, "plain" 대신 "html" 형식으로 설정하고, 링크를 <a> 태그로 작성하면 된다.
항목 | 설명 |
target="_blank" | 새 창에서 열기 |
text/html | HTML 형식으로 이메일 보내기 |
MIMEMultipart("alternative") | plain + html 모두 허용 (스팸 차단 줄이기) |
◆ email.service.py
이번엔 email.service.py 파일에 비밀번호 변경 시 이메일을 발송하는 함수를 정의해 준다.
# EmailServiceSendNewPW 클래스 추가
from service.email_class import 기존 이메일 클래스들, EmailServiceSendNewPW
# 비밀번호 변경 안내 이메일 전송
def send_changed_pw_email(email: str, username: str):
# 이메일 전송
email_service = EmailServiceSendNewPW() # EmailServiceSendNewPW 클래스 인스턴스 생성
try:
email_service.send_email(receiver_email=email, username=username) # 이메일 전송
logger.info(f"비밀번호 변경 안내 이메일을 {email}로 전송했습니다.")
except Exception as e:
# 이메일 전송 실패 시 에러 메시지 반환
logger.error(f"이메일 전송 실패: {e}") # 에러 로깅
◆ users_controller.py
- email.service.py 의 비밀번호 변경 코드도 수정해 준다.
- 먼저 service 디렉터리의 email_service.py 내에 앞서 생성한 send_cahnged_pw_email 함수를 import 해준다.
- 비밀번호 업데이트 처리하는 함수인 update_user_password() 함수와 return 사이에 send_cahnged_pw_email 함수를 추가해 주고, 예외 처리와 로깅 부분을 추가해 준다.
- 함수의 인자 중 하나인 email의 경우, UserUpdate 클래스에 추가하게 되면 수정해야 할 로직들이 많아지므로, user db에서 불러오도록 한다.
# send_changed_pw_email 함수 추가
from service.email_service import 기존 함수들, send_changed_pw_email
# 비밀번호 변경
@router.put("/change_pw")
async def update_password(req: UserUpdate, db: Session = Depends(get_db)):
username = req.username
current_password = req.current_password # 현재 비밀번호
new_password = req.new_password # 새로운 비밀번호
new_password_confirm = req.new_password_confirm # 새로운 비밀번호 확인
.....
# 비밀번호 업데이트 처리
update_user_password(db, user, new_password) # 기존 함수를 호출하여 비밀번호 업데이트
# 비밀번호 변경 안내 이메일 발송
try:
send_changed_pw_email(email=user.email, username=username)
logger.info(f"비밀번호 변경 안내 이메일을 {user.email}로 성공적으로 전송했습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
raise HTTPException(status_code=500, detail="이메일 전송에 실패했습니다. 다시 시도해 주세요")
return {"success": True, "message": "비밀번호가 성공적으로 변경되었습니다."}
비밀번호 변경 이메일 테스트
main.py를 실행하여 서버를 실행한 뒤, 비밀번호를 변경해 본다.

확인 이메일이 잘 발송되고, 이메일 내의 링크를 클릭하면 비밀번호 변경하는 사이트로 이동된다.

email_class.py 파일 내
다른 클래스들 보완
테스트가 성공한 것을 확인했으니, email_class.py 파일 내의 다른 클래스들도 이메일 발송 내용을 링크를 포함한 내용으로 바꿔주도록 한다.
◆ 회원 가입 환영 이메일 클래스
회원 가입 시 전송되는 이메일에 홈페이지 링크를 추가하여 로그인을 할 수 있도록 수정해 주었다.
# 회원가입 환영 이메일
class EmailServiceWelcome:
def send_email(
self,
receiver_email: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart("alternative")
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "회원 가입을 환영합니다."
# HTML 본문 구성
html_body = f"""
<html>
<body>
<p><strong>메모 앱</strong> 회원가입을 환영합니다!</p>
<p>앞으로 많은 이용 부탁 드리며, 사용에 불편함이 있거나, 제안사항이 있으시면</p>
<p>언제든 피드백 부탁 드립니다! 👍🏻</p>
<p><a href="http://localhost:8000" target="_blank">✅ 로그인하러 가기</a></p>
</body>
</html>
"""
message.attach(MIMEText(html_body, "html"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"회원가입 이메일이 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
◆ 회원 탈퇴 이메일 클래스
회원 탈퇴 시 전송되는 이메일에 홈페이지 링크를 추가하여 로그인을 할 수 있도록 수정해 주었다.
# 회원 탈퇴 이메일
class EmailServiceBye:
def send_email(
self,
receiver_email: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart("alternative")
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "탈퇴 완료 이메일."
# HTML 본문 구성
html_body = f"""
<html>
<body>
<p><strong>메모 앱</strong> 회원 탈퇴가 완료되었습니다.</p>
<p>그동안 고객님의 이용에 감사 드리며, 더 나은 서비스가 되도록 노력하겠습니다.🙇🏻♀️</p>
<p>곧 고객님을 다시 만날 날을 기다리겠습니다. 🙋🏻♂️</p>
<p><a href="http://localhost:8000" target="_blank">✅ 회원가입하러 가기</a></p>
</body>
</html>
"""
message.attach(MIMEText(html_body, "html"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"탈퇴 안내 이메일이 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
◆ 아이디 찾기 이메일 클래스
이메일 찾기 요청 시 전송되는 이메일에 홈페이지 링크를 추가하여 로그인을 할 수 있도록 수정해 주었다.
# 아이디 찾기 이메일
class EmailServiceFindId:
def send_email(
self,
receiver_email: str,
username: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart("alternative")
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "아이디 찾기 이메일."
# HTML 본문 구성
html_body = f"""
<html>
<body>
<p>사용자님의 아이디는 <strong>{username}</strong> 입니다.</p>
<p><a href="http://localhost:8000" target="_blank">✅ 로그인하러 가기</a></p>
</body>
</html>
"""
message.attach(MIMEText(html_body, "html"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"아이디가 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
◆ 임시비밀번호 안내 이메일 클래스
임시비밀번호 생성 요청 시 전송되는 이메일에 비밀번호를 변경하는 링크를 추가하여 비밀번호를 변경할 수 있도록 수정해 주었다.
# 임시 비밀번호 이메일
class EmailServiceSendTempPW:
def send_email(
self,
receiver_email: str,
username: str,
temp_password: str,
):
sender_email = os.getenv('EMAIL_ADDRESS')
password = os.getenv('EMAIL_PASSWORD')
message = MIMEMultipart("alternative")
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "임시 비밀번호 이메일"
# HTML 본문 구성
html_body = f"""
<html>
<body>
<p><strong>{username}</strong>님의 임시비밀번호는 {temp_password} 입니다.</p>
<p>해당 비밀번호는 임시 비밀번호이므로, 비밀번호를 변경하는 것을 권장 드립니다.</p>
<p>아래 링크를 통해 비밀번호를 변경해 주세요.</p>
<p><a href="http://localhost:8000/change_pw" target="_blank">➡️ 비밀번호 변경하러 가기</a></p>
</body>
</html>
"""
message.attach(MIMEText(html_body, "html"))
try:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, password)
server.send_message(message)
logger.info(f"임시 비밀번호가 {receiver_email}로 성공적으로 전송되었습니다.")
except Exception as e:
logger.error(f"이메일 전송 실패: {e}")
테스트 하기
main.py를 실행하여 서버를 실행한 뒤, 이메일 내용을 확인하고, 지정한 링크로 제대로 이동하는지 테스트해본다.
◆ 아이디 찾기

◆ 회원 탈퇴

◆ 회원 가입

◆ 임시 비밀번호

잘 구현된다.
다음 내용
[파이썬] FastAPI - 메모 앱 프로젝트 16: 메인 페이지 나누기
이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 15: 이메일 기능 보완이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 14: 비밀번호 찾기(수정)이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾
puppy-foot-it.tistory.com
'[파이썬 Projects] > <파이썬 웹개발>' 카테고리의 다른 글
[파이썬] FastAPI - 메모 앱 프로젝트 17: 소셜 로그인 탈퇴 (0) | 2025.05.15 |
---|---|
[파이썬] FastAPI - 메모 앱 프로젝트 16: 메인 페이지 나누기 (1) | 2025.05.14 |
[파이썬] FastAPI - 메모 앱 프로젝트 14: 비밀번호 찾기(수정) (0) | 2025.05.13 |
[파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기 (0) | 2025.05.13 |
[파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기 (0) | 2025.05.13 |