0

I am trying to write a newsletter tool so I can send the same email to multiple users. It should take as input a string containing text, html and some custom BBcode-tags and send the mail offering primary the mail content as Html-Mail and as secondary backup as plain text.

The desired output should look like this:

Lieber Eric

Herzlich willkommen beim 'Eye Contact Experiment'. Wir werden uns am xx.yy.zzzz um 19:00 an XYZ treffen.

Wir freuen uns auf dich.

Liebe Grüsse,
Darth Vader
[Social Freedom Crew](https://www.socialfreedomcrew.com/)

What I actually receive is this:

From: myself@gmail.com
To: recepient@gmail.com
MIME-Version: 1.0
Content-Type: multipart/alternative;
 boundary="===============5497262035116171126=="

--===============5497262035116171126==
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

Lieber Ace

Herzlich willkommen bei '36 Fragen zum Verlieben'. Wir werden uns am xx.yy.zz=
zz um 19:00 an XYZ treffen.

Wir freuen uns auf dich.

Liebe Gr=C3=BCsse,
Darth Vader
Social Freedom Crew

--===============5497262035116171126==
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0



Lieber Ace

Herzlich willkommen beim 'Eye Contact Experiment'. Wir werden uns am xx.yy.zz=
zz um 19:00 an XYZ treffen.

Wir freuen uns auf dich.

Liebe Gr=C3=BCsse,
Darth Vader
<a href=3D"www.socialfreedomcrew.com/">Social Freedom Crew</a>

--===============5497262035116171126==--

The two issues I face are:

  1. I only want the HTML version of the email shown, or if html is disabled, the plain text alternative
  2. I can see issues in the text encoding (Gr=C3=BCsse which should be shown as the German word 'Grüsse')

This is the code I have written so far:

import smtplib as smtp
from email.message import EmailMessage

# some code parsing the initial message and a list of recepients
class IoManager:
    def __init__(self) -> None:
        pass

    def read_csv(self, file_path='ignore/ticketexport.csv') -> list[Contact]:
        with open(file_path, mode='r') as file:
            # reading the CSV file
            csvFile = csv.DictReader(file)

            contacts: list[Contact] = []
            for line in csvFile:
                contacts.append(
                    Contact(firstname=line['Vorname'], email_address=line['E-Mail']))
            return contacts

    def read_txt(self, file_path: str = 'input/message.txt') -> str:
        with open(file_path, mode='r', encoding='utf-8') as file:
            return str(file.read())

class Content_Parser:
    def __init__(self) -> None:
        pass

    def convert(self, content_string):
        pass


class BBCode_Parser(Content_Parser):
    Tag_Dictionary = {
        # Identifier: regex_string
        'FIRSTNAME': "\[firstname]"
    }

    def __init__(self) -> None:
        super().__init__()

    def convert(self, content_string: str, regex: str, data: str) -> str:
        '''
        Converts BBCode filtered by regex expression with data provided.
        '''
        # super().convert()
        return re.sub(regex, data, content_string)


class HTML_Remover(Content_Parser):
    def __init__(self) -> None:
        super().__init__()

    def convert(self, content_html: str):
        '''
        Removes all HTML tags from content_html.
        '''
        soup = BeautifulSoup(content_html, "html5lib")
        return soup.get_text()

class Email:
    
    def __init__(self) -> None:
        self.msg = EmailMessage()

    def compose_mail(self, subject, sender, contact: Contact):
        # Prepare message content:
        self.msg['Subject'] = subject
        self.msg['From'] = sender
        self.msg['To'] = contact.email_address
        self.msg.set_content(contact.content_plain)
        self.msg.add_alternative(contact.content_html, subtype='html')
        return self.msg

class SmtpManager:
    EMAIL_USER = credentials.SMPT_ACCESS_CREDENTIALS['GMAIL']['EMAIL_ADDRESS']
    EMAIL_PASSWORD = credentials.SMPT_ACCESS_CREDENTIALS['GMAIL']['EMAIL_PASSWORD']

    SMTP_SERVER_ADDRESS = smtp_config.SMTP_SERVERS_ADDRESS['GMAIL']['URL']
    SMTP_PORT = smtp_config.SMTP_SERVERS_ADDRESS['GMAIL']['SSL_PORT']

    def __init__(self) -> None:
        self.smtp = smtp
           

    def send_mails(self, emails: list[Email]):
        # make smtp url and port selectable
        with self.smtp.SMTP_SSL(SmtpManager.SMTP_SERVER_ADDRESS, SmtpManager.SMTP_PORT) as smtp:
            smtp.login(SmtpManager.EMAIL_USER, SmtpManager.EMAIL_PASSWORD)
            for email in emails:
                smtp.send_message(email.msg)



def main():
    print('main:')
    # Some code to fetch all receivers for the mail
   
    # Convert raw message to html and plain text content of mail
    raw_message = IoManager().read_txt()    
    subject_body: dict = Txt_Splitter().convert(raw_message)
    subject = subject_body['subject']
    raw_message = subject_body['body']
    
    bbcode_parser = BBCode_Parser()
    html_remover = HTML_Remover()
    for contact in contacts:
        contact.content_html = bbcode_parser.convert(
            raw_message,
            BBCode_Parser.Tag_Dictionary['FIRSTNAME'], # transforms [firstname]into e.g John
            contact.firstname
        )
        contact.content_plain = html_remover.convert(str(contact.content_html))

    # Compose Email
    sender = credentials.SMPT_ACCESS_CREDENTIALS['GMAIL']['EMAIL_ADDRESS']
    emails: list[Email] = []
    for contact in contacts:
        mail = Email()
        mail.compose_mail(subject, sender, contact)
        emails.append(mail)

    # send emails
    smtp_manager = SmtpManager()
    smtp_manager.send_mails(emails)


if __name__ == '__main__':
    main()

MIMEMultipart and sendmail() did not work either

I also tried out with using MIMEMultipart instead of EmailMessage and sendmail() instead of send_message(). This approach has been suggested here and lead to sending only the Html-part of the content but al line breaks in the string were lost, which is undesireable.

What have I overlooked and thus should do to to make it work the way I want?

  • 2
    This problem seems to lie in the creation of "contact.content_plain" which isn't shown in your code. – Michael Butscher Nov 12 '22 at 11:44
  • Thank you for your reply, Michael. I added some code snippets to my original posts so that it will hopefully give an idea where the reason for the undesired output occurs. – AceVanCleef Nov 15 '22 at 19:40

0 Answers0