/
email_task.py
186 lines (169 loc) · 7.54 KB
/
email_task.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
import os
import ssl
import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import Any, cast, List
from prefect import Task
from prefect.client import Secret
from prefect.utilities.tasks import defaults_from_attrs
class EmailTask(Task):
"""
Task for sending email from an authenticated email service over SMTP. For this task to
function properly you must have the `"EMAIL_USERNAME"` and `"EMAIL_PASSWORD"`
Prefect Secrets set. It is recommended you use a [Google App
Password](https://support.google.com/accounts/answer/185833) if you use Gmail. The default
SMTP server is set to the Gmail SMTP server on port 465 (SMTP-over-SSL). Sending messages
containing HTML code is supported - the default MIME type is set to the text/html.
You can also use `smtp_type="INSECURE"` and `smtp_port=25` to use an insecure, internal SMTP server.
The `"EMAIL_USERNAME"` and `"EMAIL_PASSWORD"` secrets are not required in this case.
Args:
- subject (str, optional): the subject of the email; can also be provided at runtime
- msg (str, optional): the contents of the email, added as html; can be used in
combination of msg_plain; can also be provided at runtime
- email_to (str, optional): the destination email address to send the message to; can also
be provided at runtime
- email_from (str, optional): the email address to send from; defaults to
notifications@prefect.io
- smtp_server (str, optional): the hostname of the SMTP server; defaults to smtp.gmail.com
- smtp_port (int, optional): the port number of the SMTP server; defaults to 465
- smtp_type (str, optional): either SSL, STARTTLS, or INSECURE; defaults to SSL
- msg_plain (str, optional): the contents of the email, added as plain text can be used in
combination of msg; can also be provided at runtime
- email_to_cc (str, optional): additional email address to send the message to as cc;
can also be provided at runtime
- email_to_bcc (str, optional): additional email address to send the message to as bcc;
can also be provided at runtime
- attachments (List[str], optional): names of files that should be sent as attachment; can
also be provided at runtime
- **kwargs (Any, optional): additional keyword arguments to pass to the base Task
initialization
"""
def __init__(
self,
subject: str = None,
msg: str = None,
email_to: str = None,
email_from: str = "notifications@prefect.io",
smtp_server: str = "smtp.gmail.com",
smtp_port: int = 465,
smtp_type: str = "SSL",
msg_plain: str = None,
email_to_cc: str = None,
email_to_bcc: str = None,
attachments: List[str] = None,
**kwargs: Any,
):
self.subject = subject
self.msg = msg
self.email_to = email_to
self.email_from = email_from
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.smtp_type = smtp_type
self.msg_plain = msg_plain
self.email_to_cc = email_to_cc
self.email_to_bcc = email_to_bcc
self.attachments = attachments or []
super().__init__(**kwargs)
@defaults_from_attrs(
"subject",
"msg",
"email_to",
"email_from",
"smtp_server",
"smtp_port",
"smtp_type",
"msg_plain",
"email_to_cc",
"email_to_bcc",
"attachments",
)
def run(
self,
subject: str = None,
msg: str = None,
email_to: str = None,
email_from: str = None,
smtp_server: str = None,
smtp_port: int = None,
smtp_type: str = None,
msg_plain: str = None,
email_to_cc: str = None,
email_to_bcc: str = None,
attachments: List[str] = None,
) -> None:
"""
Run method which sends an email.
Args:
- subject (str, optional): the subject of the email; defaults to the one provided
at initialization
- msg (str, optional): the contents of the email; defaults to the one provided
at initialization
- email_to (str, optional): the destination email address to send the message to;
defaults to the one provided at initialization
- email_from (str, optional): the email address to send from; defaults to the one
provided at initialization
- smtp_server (str, optional): the hostname of the SMTP server; defaults to the one
provided at initialization
- smtp_port (int, optional): the port number of the SMTP server; defaults to the one
provided at initialization
- smtp_type (str, optional): either SSL, STARTTLS, or INSECURE; defaults to the one provided
at initialization
- msg_plain (str, optional): the contents of the email, added as plain text can be used in
combination of msg; defaults to the one provided at initialization
- email_to_cc (str, optional): additional email address to send the message to as cc;
defaults to the one provided at initialization
- email_to_bcc (str, optional): additional email address to send the message to as bcc;
defaults to the one provided at initialization
- attachments (List[str], optional): names of files that should be sent as attachment;
defaults to the one provided at initialization
Returns:
- None
"""
if smtp_type != "INSECURE":
username = cast(str, Secret("EMAIL_USERNAME").get())
password = cast(str, Secret("EMAIL_PASSWORD").get())
message = MIMEMultipart()
message["Subject"] = subject
message["From"] = email_from
message["To"] = email_to
if email_to_cc:
message["Cc"] = email_to_cc
if email_to_bcc:
message["Bcc"] = email_to_bcc
# First add the message in plain text, then the HTML version. Email clients try to render
# the last part first
if msg_plain:
message.attach(MIMEText(msg_plain, "plain"))
if msg:
message.attach(MIMEText(msg, "html"))
for filepath in attachments:
with open(filepath, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
filename = os.path.basename(filepath)
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
message.attach(part)
if smtp_type == "INSECURE":
server = smtplib.SMTP(smtp_server, smtp_port)
else:
context = ssl.create_default_context()
if smtp_type == "SSL":
server = smtplib.SMTP_SSL(smtp_server, smtp_port, context=context)
elif smtp_type == "STARTTLS":
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls(context=context)
else:
raise ValueError(f"{smtp_type} is an unsupported value for smtp_type.")
server.login(username, password)
try:
server.send_message(message)
finally:
server.quit()