Coverage for gws-app/gws/plugin/email_helper/__init__.py: 0%
93 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Email sending helper."""
3import email.message
4import email.policy
5import email.utils
6import smtplib
7import ssl
9import gws
11gws.ext.new.helper('email')
14class SmtpMode(gws.Enum):
15 """SMTP connection modes."""
16 plain = 'plain'
17 """Plain connection without encryption."""
18 ssl = 'ssl'
19 """SSL connection."""
20 tls = 'tls'
21 """TLS connection (STARTTLS)."""
24class SmtpConfig(gws.Config):
25 """SMTP server configuration."""
27 mode: SmtpMode = SmtpMode.ssl
28 """Connection mode."""
29 host: str
30 """SMTP host name"""
31 port: int = 0
32 """SMTP port."""
33 login: str = ''
34 """Login"""
35 password: str = ''
36 """Password."""
37 timeout: gws.Duration = '30'
38 """Connection timeout."""
41class Config(gws.Config):
42 """Mail helper settings"""
44 smtp: SmtpConfig
45 """SMTP server configuration."""
46 mailFrom: str = ''
47 """Default 'From' address."""
50class Message(gws.Data):
51 """Email message."""
53 subject: str
54 """Subject."""
55 mailTo: str
56 """To addresses, comma separated."""
57 mailFrom: str
58 """From address (default if omitted)."""
59 bcc: str
60 """Bcc addresses."""
61 text: str
62 """Plain text content."""
63 html: str
64 """HTML content."""
67class Error(gws.Error):
68 pass
71##
73_DEFAULT_POLICY = {
74 'linesep': '\r\n',
75 'cte_type': '7bit',
76 'utf8': False,
77}
79_DEFAULT_ENCODING = 'quoted-printable'
81_DEFAULT_PORT = {
82 SmtpMode.plain: 25,
83 SmtpMode.ssl: 465,
84 SmtpMode.tls: 587,
85}
88class _SmtpServer(gws.Data):
89 mode: SmtpMode
90 host: str
91 port: int
92 login: str
93 password: str
94 timeout: int
97class Object(gws.Node):
98 smtp: _SmtpServer
99 mailFrom: str
101 def configure(self):
102 self.mailFrom = self.cfg('mailFrom')
104 p = self.cfg('smtp')
106 self.smtp = _SmtpServer(
107 mode=p.mode or SmtpMode.ssl,
108 host=p.host,
109 login=p.login,
110 password=p.password,
111 timeout=p.timeout,
112 )
113 self.smtp.port = p.port or _DEFAULT_PORT.get(self.smtp.mode)
115 def send_mail(self, m: Message):
116 msg = email.message.EmailMessage(email.policy.EmailPolicy(**_DEFAULT_POLICY))
118 msg['Subject'] = m.subject
119 msg['To'] = m.mailTo
120 msg['From'] = m.mailFrom or self.mailFrom
121 msg['Date'] = email.utils.formatdate()
122 if m.bcc:
123 msg['Bcc'] = m.bcc
125 msg.set_content(m.text, cte=_DEFAULT_ENCODING)
126 if m.html:
127 # @TODO images
128 msg.add_alternative(m.html, subtype='html', cte=_DEFAULT_ENCODING)
130 self._send(msg)
132 def _send(self, msg):
133 if self.smtp:
134 try:
135 with self._smtp_connection() as conn:
136 conn.send_message(msg)
137 except OSError as exc:
138 raise Error('SMTP error') from exc
140 def _smtp_connection(self):
141 if self.smtp.mode == SmtpMode.ssl:
142 conn = smtplib.SMTP_SSL(
143 host=self.smtp.host,
144 port=self.smtp.port,
145 timeout=self.smtp.timeout,
146 context=ssl.create_default_context(),
147 )
148 else:
149 conn = smtplib.SMTP(
150 host=self.smtp.host,
151 port=self.smtp.port,
152 timeout=self.smtp.timeout,
153 )
155 # conn.set_debuglevel(2)
157 if self.smtp.mode == SmtpMode.tls:
158 conn.starttls(context=ssl.create_default_context())
160 if self.smtp.login:
161 conn.login(self.smtp.login, self.smtp.password)
163 return conn