Skip to content

Mail


Email handling module for user communications.

This module provides functionality for sending emails, managing SMTP connections, and handling email verification tokens.

Classes:

Name Description
MailSender

Email sending class with SMTP connection management

Functions:

Name Description
send_mail

Send email with HTML content

generate_confirmation_token

Generate email verification token

confirm_token

Verify email confirmation token

send_confirmation_mail_with_link

Send verification email with confirmation link

send_confirmation_mail

Send verification email with confirmation code

MailSender

Email sender with SMTP server connection management.

Attributes:

Name Type Description
username str

SMTP server login username

password str

SMTP server login password

server_name str

SMTP server hostname

server_port int

SMTP server port

use_SSL bool

Whether to use SSL connection

smtpserver SMTP

SMTP server connection

connected bool

Connection status

recipients list

List of recipient email addresses

html_ready bool

Whether HTML content is enabled

msg MIMEMultipart

Email message content

Warning

SMTP servers use different ports for SSL and TLS.

Methods:

Name Description
set_message

Set email content and metadata

clear_message

Clear email content

connect

Connect to SMTP server

send_all

Send email to all recipients

Source code in apps/annotator/code/forms/mail.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
class MailSender:
    """
    Email sender with SMTP server connection management.

    Attributes
    ----------
    username : str
        SMTP server login username
    password : str
        SMTP server login password
    server_name : str
        SMTP server hostname
    server_port : int
        SMTP server port
    use_SSL : bool
        Whether to use SSL connection
    smtpserver : smtplib.SMTP
        SMTP server connection
    connected : bool
        Connection status
    recipients : list
        List of recipient email addresses
    html_ready : bool
        Whether HTML content is enabled
    msg : MIMEMultipart
        Email message content

    Warning
    -------
    SMTP servers use different ports for SSL and TLS.

    Methods
    -------
    set_message(plaintext, subject, from, htmltext)
        Set email content and metadata
    clear_message()
        Clear email content
    connect()
        Connect to SMTP server
    send_all(close_connection)
        Send email to all recipients
    """
    def __init__(self, in_username, in_password, in_server=("smtp.gmail.com", 587), use_SSL=False):
        """
        Initialize mail sender with server configuration.

        Parameters
        ----------
        in_username : str
            SMTP server username
        in_password : str
            SMTP server password
        in_server : tuple, optional
            (hostname, port) for SMTP server
        use_SSL : bool, optional
            Whether to use SSL instead of TLS
        """
        self.username = in_username
        self.password = in_password
        self.server_name = in_server[0]
        self.server_port = in_server[1]
        self.use_SSL = use_SSL

        if self.use_SSL:
            self.smtpserver = smtplib.SMTP_SSL(self.server_name, self.server_port)
        else:
            self.smtpserver = smtplib.SMTP(self.server_name, self.server_port)
        self.connected = False
        self.recipients = []

    def __str__(self):
        return "Type: Mail Sender \n" \
               "Connection to server {}, port {} \n" \
               "Connected: {} \n" \
               "Username: {}, Password: {}".format(self.server_name, self.server_port, self.connected, self.username, self.password)

    def set_message(self, in_plaintext, in_subject="", in_from=None, in_htmltext=None):
        """
        Create MIME message with optional HTML content.

        Parameters
        ----------
        in_plaintext : str
            Plain text email body
        in_subject : str, optional
            Email subject line
        in_from : str, optional
            Sender email address
        in_htmltext : str, optional
            HTML version of email body
        """

        if in_htmltext is not None:
            self.html_ready = True
        else:
            self.html_ready = False

        if self.html_ready:
            self.msg = MIMEMultipart('alternative')  # 'alternative' allows attaching an html version of the message later
            self.msg.attach(MIMEText(in_plaintext, 'plain'))
            self.msg.attach(MIMEText(in_htmltext, 'html'))
        else:
            self.msg = MIMEText(in_plaintext, 'plain')

        self.msg['Subject'] = in_subject
        if in_from is None:
            self.msg['From'] = self.username
        else:
            self.msg['From'] = in_from
        self.msg["To"] = None
        self.msg["CC"] = None
        self.msg["BCC"] = None

    def clear_message(self):
        """
        Remove all email body content.

        Clears both plain text and HTML content if present.
        """
        self.msg.set_payload("")

    def set_subject(self, in_subject):
        """
        Set email subject line.

        Parameters
        ----------
        in_subject : str
            New subject line
        """
        self.msg.replace_header("Subject", in_subject)

    def set_from(self, in_from):
        """
        Set sender email address.

        Parameters
        ----------
        in_from : str
            Sender email address
        """
        self.msg.replace_header("From", in_from)

    def set_plaintext(self, in_body_text):
        """
        Set plain text email body.

        Parameters
        ----------
        in_body_text : str
            Plain text content

        Warning
        -------
        Replaces entire payload if no HTML content exists
        """
        if not self.html_ready:
            self.msg.set_payload(in_body_text)
        else:
            payload = self.msg.get_payload()
            payload[0] = MIMEText(in_body_text)
            self.msg.set_payload(payload)

    def set_html(self, in_html):
        """
        Set HTML email body.

        Parameters
        ----------
        in_html : str
            HTML content

        Raises
        ------
        TypeError
            If HTML wasn't enabled in set_message()
        """
        try:
            payload = self.msg.get_payload()
            payload[1] = MIMEText(in_html, 'html')
            self.msg.set_payload(payload)
        except TypeError:
            print("ERROR: "
                  "Payload is not a list. Specify an HTML message with in_htmltext in MailSender.set_message()")
            raise

    def set_recipients(self, in_recipients):
        """
        Set list of recipient email addresses.

        Parameters
        ----------
        in_recipients : list
            List of recipient email addresses

        Raises
        ------
        TypeError
            If input is not a list or tuple
        """
        if not isinstance(in_recipients, (list, tuple)):
            raise TypeError("Recipients must be a list or tuple, is {}".format(type(in_recipients)))

        self.recipients = in_recipients

    def add_recipient(self, in_recipient):
        """
        Add single recipient to list.

        Parameters
        ----------
        in_recipient : str
            Recipient email address
        """
        self.recipients.append(in_recipient)

    def connect(self):
        """
        Connect to SMTP server.

        Establishes connection using configured credentials.
        Must be called before sending messages.
        """
        if not self.use_SSL:
            self.smtpserver.starttls()
        self.smtpserver.login(self.username, self.password)
        self.connected = True
        print("Connected to {}".format(self.server_name))

    def disconnect(self):
        """
        Close SMTP server connection.
        """
        self.smtpserver.close()
        self.connected = False

    def send_all(self, close_connection=True):
        """
        Send message to all recipients.

        Parameters
        ----------
        close_connection : bool, optional
            Whether to close connection after sending

        Raises
        ------
        ConnectionError
            If not connected to SMTP server
        """
        if not self.connected:
            raise ConnectionError("Not connected to any server. Try self.connect() first")

        print("Message: {}".format(self.msg.get_payload()))

        for recipient in self.recipients:
                self.msg.replace_header("To", recipient)
                print("Sending to {}".format(recipient))
                self.smtpserver.send_message(self.msg)

        print("All messages sent")

        if close_connection:
            self.disconnect()
            print("Connection closed")

__init__(in_username, in_password, in_server=('smtp.gmail.com', 587), use_SSL=False)

Initialize mail sender with server configuration.

Parameters:

Name Type Description Default
in_username str

SMTP server username

required
in_password str

SMTP server password

required
in_server tuple

(hostname, port) for SMTP server

('smtp.gmail.com', 587)
use_SSL bool

Whether to use SSL instead of TLS

False
Source code in apps/annotator/code/forms/mail.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def __init__(self, in_username, in_password, in_server=("smtp.gmail.com", 587), use_SSL=False):
    """
    Initialize mail sender with server configuration.

    Parameters
    ----------
    in_username : str
        SMTP server username
    in_password : str
        SMTP server password
    in_server : tuple, optional
        (hostname, port) for SMTP server
    use_SSL : bool, optional
        Whether to use SSL instead of TLS
    """
    self.username = in_username
    self.password = in_password
    self.server_name = in_server[0]
    self.server_port = in_server[1]
    self.use_SSL = use_SSL

    if self.use_SSL:
        self.smtpserver = smtplib.SMTP_SSL(self.server_name, self.server_port)
    else:
        self.smtpserver = smtplib.SMTP(self.server_name, self.server_port)
    self.connected = False
    self.recipients = []

add_recipient(in_recipient)

Add single recipient to list.

Parameters:

Name Type Description Default
in_recipient str

Recipient email address

required
Source code in apps/annotator/code/forms/mail.py
242
243
244
245
246
247
248
249
250
251
def add_recipient(self, in_recipient):
    """
    Add single recipient to list.

    Parameters
    ----------
    in_recipient : str
        Recipient email address
    """
    self.recipients.append(in_recipient)

clear_message()

Remove all email body content.

Clears both plain text and HTML content if present.

Source code in apps/annotator/code/forms/mail.py
150
151
152
153
154
155
156
def clear_message(self):
    """
    Remove all email body content.

    Clears both plain text and HTML content if present.
    """
    self.msg.set_payload("")

connect()

Connect to SMTP server.

Establishes connection using configured credentials. Must be called before sending messages.

Source code in apps/annotator/code/forms/mail.py
253
254
255
256
257
258
259
260
261
262
263
264
def connect(self):
    """
    Connect to SMTP server.

    Establishes connection using configured credentials.
    Must be called before sending messages.
    """
    if not self.use_SSL:
        self.smtpserver.starttls()
    self.smtpserver.login(self.username, self.password)
    self.connected = True
    print("Connected to {}".format(self.server_name))

disconnect()

Close SMTP server connection.

Source code in apps/annotator/code/forms/mail.py
266
267
268
269
270
271
def disconnect(self):
    """
    Close SMTP server connection.
    """
    self.smtpserver.close()
    self.connected = False

send_all(close_connection=True)

Send message to all recipients.

Parameters:

Name Type Description Default
close_connection bool

Whether to close connection after sending

True

Raises:

Type Description
ConnectionError

If not connected to SMTP server

Source code in apps/annotator/code/forms/mail.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
def send_all(self, close_connection=True):
    """
    Send message to all recipients.

    Parameters
    ----------
    close_connection : bool, optional
        Whether to close connection after sending

    Raises
    ------
    ConnectionError
        If not connected to SMTP server
    """
    if not self.connected:
        raise ConnectionError("Not connected to any server. Try self.connect() first")

    print("Message: {}".format(self.msg.get_payload()))

    for recipient in self.recipients:
            self.msg.replace_header("To", recipient)
            print("Sending to {}".format(recipient))
            self.smtpserver.send_message(self.msg)

    print("All messages sent")

    if close_connection:
        self.disconnect()
        print("Connection closed")

set_from(in_from)

Set sender email address.

Parameters:

Name Type Description Default
in_from str

Sender email address

required
Source code in apps/annotator/code/forms/mail.py
169
170
171
172
173
174
175
176
177
178
def set_from(self, in_from):
    """
    Set sender email address.

    Parameters
    ----------
    in_from : str
        Sender email address
    """
    self.msg.replace_header("From", in_from)

set_html(in_html)

Set HTML email body.

Parameters:

Name Type Description Default
in_html str

HTML content

required

Raises:

Type Description
TypeError

If HTML wasn't enabled in set_message()

Source code in apps/annotator/code/forms/mail.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def set_html(self, in_html):
    """
    Set HTML email body.

    Parameters
    ----------
    in_html : str
        HTML content

    Raises
    ------
    TypeError
        If HTML wasn't enabled in set_message()
    """
    try:
        payload = self.msg.get_payload()
        payload[1] = MIMEText(in_html, 'html')
        self.msg.set_payload(payload)
    except TypeError:
        print("ERROR: "
              "Payload is not a list. Specify an HTML message with in_htmltext in MailSender.set_message()")
        raise

set_message(in_plaintext, in_subject='', in_from=None, in_htmltext=None)

Create MIME message with optional HTML content.

Parameters:

Name Type Description Default
in_plaintext str

Plain text email body

required
in_subject str

Email subject line

''
in_from str

Sender email address

None
in_htmltext str

HTML version of email body

None
Source code in apps/annotator/code/forms/mail.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def set_message(self, in_plaintext, in_subject="", in_from=None, in_htmltext=None):
    """
    Create MIME message with optional HTML content.

    Parameters
    ----------
    in_plaintext : str
        Plain text email body
    in_subject : str, optional
        Email subject line
    in_from : str, optional
        Sender email address
    in_htmltext : str, optional
        HTML version of email body
    """

    if in_htmltext is not None:
        self.html_ready = True
    else:
        self.html_ready = False

    if self.html_ready:
        self.msg = MIMEMultipart('alternative')  # 'alternative' allows attaching an html version of the message later
        self.msg.attach(MIMEText(in_plaintext, 'plain'))
        self.msg.attach(MIMEText(in_htmltext, 'html'))
    else:
        self.msg = MIMEText(in_plaintext, 'plain')

    self.msg['Subject'] = in_subject
    if in_from is None:
        self.msg['From'] = self.username
    else:
        self.msg['From'] = in_from
    self.msg["To"] = None
    self.msg["CC"] = None
    self.msg["BCC"] = None

set_plaintext(in_body_text)

Set plain text email body.

Parameters:

Name Type Description Default
in_body_text str

Plain text content

required
Warning

Replaces entire payload if no HTML content exists

Source code in apps/annotator/code/forms/mail.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def set_plaintext(self, in_body_text):
    """
    Set plain text email body.

    Parameters
    ----------
    in_body_text : str
        Plain text content

    Warning
    -------
    Replaces entire payload if no HTML content exists
    """
    if not self.html_ready:
        self.msg.set_payload(in_body_text)
    else:
        payload = self.msg.get_payload()
        payload[0] = MIMEText(in_body_text)
        self.msg.set_payload(payload)

set_recipients(in_recipients)

Set list of recipient email addresses.

Parameters:

Name Type Description Default
in_recipients list

List of recipient email addresses

required

Raises:

Type Description
TypeError

If input is not a list or tuple

Source code in apps/annotator/code/forms/mail.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def set_recipients(self, in_recipients):
    """
    Set list of recipient email addresses.

    Parameters
    ----------
    in_recipients : list
        List of recipient email addresses

    Raises
    ------
    TypeError
        If input is not a list or tuple
    """
    if not isinstance(in_recipients, (list, tuple)):
        raise TypeError("Recipients must be a list or tuple, is {}".format(type(in_recipients)))

    self.recipients = in_recipients

set_subject(in_subject)

Set email subject line.

Parameters:

Name Type Description Default
in_subject str

New subject line

required
Source code in apps/annotator/code/forms/mail.py
158
159
160
161
162
163
164
165
166
167
def set_subject(self, in_subject):
    """
    Set email subject line.

    Parameters
    ----------
    in_subject : str
        New subject line
    """
    self.msg.replace_header("Subject", in_subject)

confirm_token(token, expiration=5600)

Verify email confirmation token.

Parameters:

Name Type Description Default
token str

Token to verify

required
expiration int

Token expiration time in seconds

5600

Returns:

Type Description
str or bool

Email address if valid, False if invalid

Source code in apps/annotator/code/forms/mail.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def confirm_token(token, expiration=5600):
    """
    Verify email confirmation token.

    Parameters
    ----------
    token : str
        Token to verify
    expiration : int, optional
        Token expiration time in seconds

    Returns
    -------
    str or bool
        Email address if valid, False if invalid
    """
    serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
    try:
        email = serializer.loads(
            token,
            salt=app.config['SECURITY_PASSWORD_SALT'],
            max_age=expiration
        )
    except:
        return False
    return email

generate_confirmation_token(email)

Generate secure token for email confirmation.

Parameters:

Name Type Description Default
email str

Email address to encode in token

required

Returns:

Type Description
str

Secure URL-safe token

Source code in apps/annotator/code/forms/mail.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def generate_confirmation_token(email):
    """
    Generate secure token for email confirmation.

    Parameters
    ----------
    email : str
        Email address to encode in token

    Returns
    -------
    str
        Secure URL-safe token
    """
    serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
    return serializer.dumps(email, app.config['SECURITY_PASSWORD_SALT'])

send_confirmation_mail(email, code)

Send verification email containing confirmation code.

Parameters:

Name Type Description Default
email str

Recipient email address

required
code str

Verification code

required
Source code in apps/annotator/code/forms/mail.py
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
def send_confirmation_mail(email, code):
    """
    Send verification email containing confirmation code.

    Parameters
    ----------
    email : str
        Recipient email address
    code : str
        Verification code
    """
    html = render_template('user/user_activate_mail.html', code=code)
    subject = "Please confirm your email"

    send_mail(email, subject, html)

Send verification email containing confirmation link.

Parameters:

Name Type Description Default
email str

Recipient email address

required
Source code in apps/annotator/code/forms/mail.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
def send_confirmation_mail_with_link(email):
    """
    Send verification email containing confirmation link.

    Parameters
    ----------
    email : str
        Recipient email address
    """
    token = generate_confirmation_token(email)
    confirm_url = url_for('confirm_email', token=token, _external=True)
    html = render_template('user/user_activate_mail_with_link.html', confirm_url=confirm_url)
    subject = "Please confirm your email"

    send_mail(email, subject, html)

send_mail(To, subject, html)

Send email with HTML content.

Parameters:

Name Type Description Default
To str

Recipient email address

required
subject str

Email subject line

required
html str

HTML email content

required
Source code in apps/annotator/code/forms/mail.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def send_mail(To, subject, html):
    """
    Send email with HTML content.

    Parameters
    ----------
    To : str
        Recipient email address
    subject : str
        Email subject line
    html : str
        HTML email content
    """
    ourmailsender = MailSender(EMAIL_ACCOUNT, EMAIL_PASSWORD, ('smtp.gmail.com', 587))

    ourmailsender.set_message("edurell", subject, "EKEEL Annotations", html)

    ourmailsender.set_recipients([To])

    ourmailsender.connect()
    ourmailsender.send_all()