diff --git a/app/lib_general.py b/app/lib_general.py index cbad113..8616562 100644 --- a/app/lib_general.py +++ b/app/lib_general.py @@ -412,6 +412,7 @@ def send_email( to_email: str, subject: str, body_html: str, + from_name: str = '', reply_to_email: str = '', reply_to_name: str = '', @@ -421,6 +422,8 @@ def send_email( bcc_email: str = '', bcc_name: str = '', body_text: str = '', + + test: bool = False ): log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) @@ -430,21 +433,76 @@ def send_email( message['Subject'] = subject else: return False - if from_email: + + if from_email and from_name: #message['From'] = Address(display_name=from_name, username=from_email.split('@')[0], domain=from_email.split('@')[1]) - message['From'] = Address(display_name=from_name, addr_spec=from_email) + try: + message['From'] = Address(display_name=from_name, addr_spec=from_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + elif from_email: + try: + message['From'] = Address(display_name=from_email, addr_spec=from_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False else: return False - if reply_to_email: - message['Reply-To'] = Address(display_name=reply_to_name, addr_spec=reply_to_email) - if to_email: - message['To'] = Address(display_name=to_name, addr_spec=to_email) + + if reply_to_email and reply_to_name: + try: + message['Reply-To'] = Address(display_name=reply_to_name, addr_spec=reply_to_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + elif reply_to_email: + try: + message['Reply-To'] = Address(display_name=reply_to_email, addr_spec=reply_to_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + + if to_email and to_name: + try: + message['To'] = Address(display_name=to_name, addr_spec=to_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + elif to_email: + try: + message['To'] = Address(display_name=to_email, addr_spec=to_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False else: return False - if cc_email: - message['Cc'] = Address(display_name=cc_name, addr_spec=cc_email) - if bcc_email: - message['Bcc'] = Address(display_name=bcc_name, addr_spec=bcc_email) + + if cc_email and cc_name: + try: + message['Cc'] = Address(display_name=cc_name, addr_spec=cc_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + elif cc_email: + try: + message['Cc'] = Address(display_name=cc_email, addr_spec=cc_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + + if bcc_email and bcc_name: + try: + message['Bcc'] = Address(display_name=bcc_name, addr_spec=bcc_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False + elif bcc_email: + try: + message['Bcc'] = Address(display_name=bcc_email, addr_spec=bcc_email) + except Exception as e: + log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****') + return False html_version = """\ @@ -479,6 +537,21 @@ def send_email( # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.info('SMTP configuration, connect, and send') + + if test: + try: + with smtplib.SMTP_SSL(settings.SMTP['server'], settings.SMTP['port'], context=context) as server: + log.debug('[TEST] SMTP log in...') + server.login(settings.SMTP['username'], settings.SMTP['password']) + log.debug('[TEST] SMTP send message... [WILL NOT SEND IN TEST MODE]') + # server.send_message(message) + log.info('[TEST] Email sent!') + return True + except: + #except SMTPException: + log.error('[TEST] Error: unable to send email') + return False + try: with smtplib.SMTP_SSL(settings.SMTP['server'], settings.SMTP['port'], context=context) as server: log.debug('SMTP log in...') diff --git a/app/main.py b/app/main.py index 0352d08..45f8f2f 100644 --- a/app/main.py +++ b/app/main.py @@ -18,7 +18,7 @@ from . import config from app.log import log, logging # Import the routers here first: -from app.routers import aether_cfg, api_crud, api, importing, sql, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, data_store, event, event_abstract, event_badge, event_badge_importing, event_badge_template, event_device, event_exhibit, event_exhibit_tracking, event_file, event_importing, event_location, event_person, event_person_detail, event_person_tracking, event_presentation, event_presenter, event_registration, event_session, flask_cfg, fundraising, grant, hosted_file, journal, journal_entry, log_client_viewing, lookup, membership_cfg, membership_group, membership_person_group, membership_person, membership_person_profile, membership_type, membership_person_type, order, order_v3, order_line, order_cart, organization, page, person, person_user, post, post_comment, product, qr, site, site_domain, user, websockets_redis, e_confex, e_cvent, c_idaa, e_impexium, e_stripe +from app.routers import aether_cfg, api_crud, api, importing, sql, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, data_store, event, event_abstract, event_badge, event_badge_importing, event_badge_template, event_device, event_exhibit, event_exhibit_tracking, event_file, event_importing, event_location, event_person, event_person_detail, event_person_tracking, event_presentation, event_presenter, event_registration, event_session, flask_cfg, fundraising, grant, hosted_file, journal, journal_entry, log_client_viewing, lookup, membership_cfg, membership_group, membership_person_group, membership_person, membership_person_profile, membership_type, membership_person_type, order, order_v3, order_line, order_cart, organization, page, person, person_user, post, post_comment, product, qr, site, site_domain, user, util_email, websockets_redis, e_confex, e_cvent, c_idaa, e_impexium, e_stripe # from app.routers import aether_cfg, sql @@ -391,6 +391,10 @@ app.include_router( user.router, tags=['User'], ) +app.include_router( + util_email.router, + tags=['Utility: Email'], +) # app.include_router( # websockets.router, # # prefix='/websocket', diff --git a/app/models/util_email_models.py b/app/models/util_email_models.py new file mode 100644 index 0000000..ee834dc --- /dev/null +++ b/app/models/util_email_models.py @@ -0,0 +1,78 @@ +import datetime, pytz + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random +from app.lib_general import log, logging + +from app.models.common_field_schema import base_fields, default_num_bytes + +from app.models.core_object_models import Core_Std_Obj_Base + + +# ### BEGIN ### API Utility: Email Models ### Email_Send_Base() ### +# Update 2023-06-27 +class Email_Send_Base(BaseModel): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + id_random: Optional[str] = Field( + # **base_fields['email_id_random'], + alias = 'email_id_random', + ) + id: Optional[int] = Field( + alias = 'email_id' + ) + + account_id_random: Optional[str] + account_id: Optional[int] + + testing: Optional[bool] = False + + from_email: str # = 'from_test@example.com' + from_name: str = None + + to_email: str # = 'to_test@example.com' + to_name: str = None + + cc_email: str = None + cc_name: str = None + + bcc_email: str = None + bcc_name: str = None + + subject: str + + body_html: str + body_text: str = None + + # json: Optional[str] # "json" is reserved; need to change field name? json_str? + # json_str: Optional[Union[Json, None]] = Field( + # alias = 'json', + # ) + # text: Optional[str] + + # notes: Optional[str] + + _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) + + @validator('id', always=True) + def email_id_lookup(cls, v, values, **kwargs): + if isinstance(v, int) and v > 0: return v + elif id_random := values.get('id_random'): + return redis_lookup_id_random(record_id_random=id_random, table_name='email') + return None + + @validator('account_id', always=True) + def account_id_lookup(cls, v, values, **kwargs): + if isinstance(v, int) and v > 0: return v + elif id_random := values.get('account_id_random'): + return redis_lookup_id_random(record_id_random=id_random, table_name='account') + return None + + class Config: + underscore_attrs_are_private = True + allow_population_by_field_name = True + fields = base_fields +# ### END ### API Grant Models ### Grant_Base() ### \ No newline at end of file diff --git a/app/routers/util_email.py b/app/routers/util_email.py new file mode 100644 index 0000000..e2ee2b0 --- /dev/null +++ b/app/routers/util_email.py @@ -0,0 +1,77 @@ +import datetime, pytz, time +from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Response, status +from pydantic import BaseModel, EmailStr, Field +from typing import Dict, List, Optional, Set, Union + +from app.lib_general import log, logging, common_route_params, Common_Route_Params, send_email +from app.config import settings +from app.db_sql import sql_insert, sql_update, sql_insert_or_update, sql_select, sql_delete, get_id_random, redis_lookup_id_random + +from app.routers.api_crud import delete_obj_template, get_obj_template, get_obj_li_template, patch_obj_template, post_obj_template + +from app.methods.data_store_methods import create_update_data_store_obj, get_data_store_rec_list, load_data_store_obj, load_data_store_obj_w_code + +# from app.models.common_field_schema import default_num_bytes +from app.models.util_email_models import Email_Send_Base +from app.models.response_models import Resp_Body_Base, mk_resp + + +router = APIRouter() + + +# ### BEGIN ### API Utility: Email ### util_email_send_obj() ### +# Updated 2023-06-27 +@router.post('/util/email/send', response_model=Resp_Body_Base) +async def util_email_send_obj( + email_send_obj: Email_Send_Base, + + test: bool = False, + + return_obj: bool = True, + + commons: Common_Route_Params = Depends(common_route_params), + ): + log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + resp_data = {} + + from_email = email_send_obj.from_email + resp_data['from_email'] = from_email + from_name = email_send_obj.from_name + + to_email = email_send_obj.to_email + resp_data['to_email'] = to_email + to_name = email_send_obj.to_name + + bcc_email = email_send_obj.bcc_email + bcc_name = email_send_obj.bcc_name + + subject = email_send_obj.subject + resp_data['subject'] = subject[:20] + + body_html = email_send_obj.body_html + body_text = email_send_obj.body_text + + log.info('Trying send_email()...') + + # NOTE: This all works, but I think it could be done better??? + + if send_email(from_email=from_email, from_name=from_name, to_email=to_email, to_name=to_name, bcc_email=bcc_email, bcc_name=bcc_name, subject=subject, body_text=body_text, body_html=body_html, test=test): + status_code = 200 + status_message = f'Email was sent to <{to_email}>.' + else: + status_code = 400 + status_message = f'Email failed to send to <{to_email}>.' + log.info(status_message) + + if return_obj: + return mk_resp(data=resp_data, status_code=status_code, response=commons.response, status_message=status_message) + else: + if status_code == 200: + resp_data = True + else: + resp_data = False + + return mk_resp(data=resp_data, status_code=status_code, response=commons.response, status_message=status_message) +# ### END ### API Utility: Email ### util_email_send_obj() ### \ No newline at end of file