envelope 2.0.2__tar.gz → 2.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envelope
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Insert a message and attachments and send e-mail / sign / encrypt contents by a single line.
5
5
  Home-page: https://github.com/CZ-NIC/envelope
6
6
  Author: Edvard Rejthar
7
7
  Author-email: edvard.rejthar@nic.cz
8
8
  License: GNU GPLv3
9
9
  Classifier: Programming Language :: Python :: 3
10
- Requires-Python: >=3.7
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  Provides-Extra: smime
13
13
  License-File: LICENSE.txt
@@ -16,9 +16,9 @@ License-File: LICENSE.txt
16
16
 
17
17
  [![Build Status](https://github.com/CZ-NIC/envelope/actions/workflows/run-unittest.yml/badge.svg)](https://github.com/CZ-NIC/envelope/actions) [![Downloads](https://pepy.tech/badge/envelope)](https://pepy.tech/project/envelope)
18
18
 
19
- Quick layer over [python-gnupg](https://bitbucket.org/vinay.sajip/python-gnupg/src), [M2Crypto](https://m2crypto.readthedocs.io/), [smtplib](https://docs.python.org/3/library/smtplib.html), [magic](https://pypi.org/project/python-magic/) and [email](https://docs.python.org/3/library/email.html?highlight=email#module-email) handling packages. Their common use cases merged into a single function. Want to sign a text and tired of forgetting how to do it right? You do not need to know everything about GPG or S/MIME, you do not have to bother with importing keys. Do not hassle with reconnecting to an SMTP server. Do not study various headers meanings to let your users unsubscribe via a URL.
20
- You insert a message, attachments and inline images and receive signed and/or encrypted output to the file or to your recipients' e-mail.
21
- Just single line of code. With the great help of the examples below.
19
+ Quick layer over [python-gnupg](https://bitbucket.org/vinay.sajip/python-gnupg/src), [M2Crypto](https://m2crypto.readthedocs.io/), [smtplib](https://docs.python.org/3/library/smtplib.html), [magic](https://pypi.org/project/python-magic/) and [email](https://docs.python.org/3/library/email.html?highlight=email#module-email) handling packages. Their common use cases merged into a single function. Want to sign a text and tired of forgetting how to do it right? You do not need to know everything about GPG or S/MIME, you do not have to bother with importing keys. Do not hassle with reconnecting to an SMTP server. Do not study various headers meanings to let your users unsubscribe via a URL.
20
+ You insert a message, attachments and inline images and receive signed and/or encrypted output to the file or to your recipients' e-mail.
21
+ Just single line of code. With the great help of the examples below.
22
22
 
23
23
  ```python3
24
24
  Envelope("my message")
@@ -32,10 +32,10 @@ Envelope("my message")
32
32
 
33
33
  ```python3
34
34
  # Inline image
35
- Envelope("My inline image: <img src='cid:image.jpg' />")
35
+ Envelope("My inline image: <img src='cid:image.jpg' />")
36
36
  .attach(path="image.jpg", inline=True)
37
37
 
38
- # Load a message and read its attachments
38
+ # Load a message and read its attachments
39
39
  Envelope.load(path="message.eml").attachments()
40
40
  # in bash: envelope --load message.eml --attachments
41
41
  ```
@@ -81,7 +81,7 @@ Envelope.load(path="message.eml").attachments()
81
81
 
82
82
  # Installation
83
83
  * Install with a single command from [PyPi](https://pypi.org/project/envelope/)
84
- ```bash
84
+ ```bash
85
85
  pip3 install envelope
86
86
  ```
87
87
 
@@ -91,10 +91,10 @@ Envelope.load(path="message.eml").attachments()
91
91
  ```
92
92
  * Or just download the project and launch `python3 -m envelope`
93
93
  * If planning to sign/encrypt with GPG, assure you have it on the system with `sudo apt install gpg` and possibly see [Configure your GPG](#configure-your-gpg) tutorial.
94
- * If planning to use S/MIME, you should ensure some prerequisites: `sudo apt install swig && pip3 install M2Crypto`
94
+ * If planning to use S/MIME, you should ensure some prerequisites: `sudo apt install swig build-essential python3-dev libssl-dev && pip3 install M2Crypto`
95
95
  * If planning to send e-mails, prepare SMTP credentials or visit [Configure your SMTP](#configure-your-smtp) tutorial.
96
96
  * If your e-mails are to be received outside your local domain, visit [DMARC](#dmarc) section.
97
- * Package [python-magic](https://pypi.org/project/python-magic/) is used as a dependency. Due to a [well-known](https://github.com/ahupp/python-magic/blob/master/COMPAT.md) name clash with the [file-magic](https://pypi.org/project/file-magic/) package, in case you need to use the latter, don't worry to run `pip uninstall python-magic && pip install file-magic` after installing envelope which is fully compatible with both projects.
97
+ * Package [python-magic](https://pypi.org/project/python-magic/) is used as a dependency. Due to a [well-known](https://github.com/ahupp/python-magic/blob/master/COMPAT.md) name clash with the [file-magic](https://pypi.org/project/file-magic/) package, in case you need to use the latter, don't worry to run `pip uninstall python-magic && pip install file-magic` after installing envelope which is fully compatible with both projects. Both use `libmagic` under the hood which is probably already installed. However, if it is not, [install](https://github.com/ahupp/python-magic?tab=readme-ov-file#installation) `sudo apt install libmagic1`.
98
98
 
99
99
  ## Bash completion
100
100
  1. Run: `apt install bash-completion jq`
@@ -105,7 +105,7 @@ Envelope.load(path="message.eml").attachments()
105
105
  As an example, let's produce in three equal ways an `output_file` with the GPG-encrypted "Hello world" content.
106
106
  ## CLI
107
107
  Launch as a CLI application in terminal, see `envelope --help`
108
-
108
+
109
109
  ```bash
110
110
  envelope --message "Hello world" \
111
111
  --output "/tmp/output_file" \
@@ -141,7 +141,7 @@ Envelope(message="Hello world",
141
141
  Both `envelope --help` for CLI arguments help and `pydoc3 envelope` to see module arguments help should contain same information as here.
142
142
 
143
143
  ## Command list
144
- All parameters are optional.
144
+ All parameters are optional.
145
145
 
146
146
  * **--param** is used in CLI
147
147
  * **.param(value)** denotes a positional argument
@@ -149,7 +149,14 @@ All parameters are optional.
149
149
  * **Envelope(param=)** is a one-liner argument
150
150
 
151
151
  #### Any attainable contents
152
- Whenever any attainable contents is mentioned, we mean plain **text**, **bytes** or **stream** (ex: from `open()`). In *module interface*, you may use a **`Path`** object to the file. In *CLI interface*, additional flags are provided instead.
152
+ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes** or **stream** (ex: from `open()`). In *module interface*, you may use a **`Path`** object to the file. In *CLI interface*, additional flags are provided instead.
153
+
154
+ If the object is not accesible, it will immediately raise `FileNotFoundError`.
155
+ ```python3
156
+ Envelope().attach(path="file.jpg")
157
+ # Could not fetch file .../file.jpg
158
+ # FileNotFoundError: [Errno 2] No such file or directory: 'file.jpg'
159
+ ```
153
160
 
154
161
  ### Input / Output
155
162
  * **message**: Message / body text.
@@ -163,35 +170,35 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
163
170
  * `alternative`: "auto", "html", "plain" You may specify e-mail text alternative. Some e-mail readers prefer to display plain text version over HTML. By default, we try to determine content type automatically (see *mime*).
164
171
  ```python3
165
172
  print(Envelope().message("He<b>llo</b>").message("Hello", alternative="plain"))
166
-
173
+
167
174
  # (output shortened)
168
175
  # Content-Type: multipart/alternative;
169
176
  # boundary="===============0590677381100492396=="
170
- #
177
+ #
171
178
  # --===============0590677381100492396==
172
179
  # Content-Type: text/plain; charset="utf-8"
173
180
  # Hello
174
- #
181
+ #
175
182
  # --===============0590677381100492396==
176
183
  # Content-Type: text/html; charset="utf-8"
177
184
  # He<b>llo</b>
178
185
  ```
179
- * *boundary*: When specifying alternative, you may set e-mail boundary if you do not wish a random one to be created.
186
+ * *boundary*: When specifying alternative, you may set e-mail boundary if you do not wish a random one to be created.
180
187
  * **.body(path=None)**: Alias of `.message` (without `alternative` and `boundary` parameter)
181
188
  * **.text(path=None)**: Alias of `.message` (without `alternative` and `boundary` parameter)
182
189
  * **Envelope(message=)**: [Any attainable contents](#any-attainable-contents)
183
-
190
+
184
191
  Equivalents for setting a string (in *Python* and in *Bash*).
185
192
  ```python3
186
193
  Envelope(message="hello") == Envelope().message("hello")
187
194
  ```
188
195
  ```bash
189
196
  envelope --message "hello"
190
- ```
197
+ ```
191
198
  Equivalents for setting contents of a file (in *Python* and in *Bash*).
192
199
  ```python3
193
200
  from pathlib import Path
194
- Envelope(message=Path("file.txt")) == Envelope(message=open("file.txt")) == Envelope.message(path="file.txt")
201
+ Envelope(message=Path("file.txt")) == Envelope(message=open("file.txt")) == Envelope.message(path="file.txt")
195
202
  ```
196
203
  ```bash
197
204
  envelope --input file.txt
@@ -205,11 +212,11 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
205
212
  repr(e)
206
213
  # WARNING: Cannot decode the message correctly, plain alternative bytes are not in Unicode.
207
214
  # Envelope(message="b'\x80'")
208
-
215
+
209
216
  # When trying to output a mal-encoded message, we end up with a ValueError exception.
210
217
  e.message()
211
218
  # ValueError: Cannot decode the message correctly, it is not in Unicode. b'\x80'
212
-
219
+
213
220
  # Setting up an encoding (even ex-post) solves the issue.
214
221
  e.header("Content-Type", "text/plain;charset=cp1250")
215
222
  e.message() # '€'
@@ -218,7 +225,7 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
218
225
  * **--output**
219
226
  * **.output(output_file)**
220
227
  * **Envelope(output=)**
221
-
228
+
222
229
  ### Recipients
223
230
  * **from**: E-mail – needed to choose our key if encrypting.
224
231
  * **--from** E-mail. Empty to read value.
@@ -227,12 +234,12 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
227
234
  * **Envelope(from_=)**: Sender e-mail or False to explicitly omit. When encrypting without sender, we do not use their key so that we will not be able to decipher again.
228
235
  ```python3
229
236
  # These statements are identical.
230
- Envelope(from_="identity@example.com")
237
+ Envelope(from_="identity@example.com")
231
238
  Envelope().from_("identity@example.com")
232
-
239
+
233
240
  # This statement produces both From header and Sender header.
234
241
  Envelope(from_="identity@example.com", headers=[("Sender", "identity2@example.com")])
235
-
242
+
236
243
  # reading an Address object
237
244
  a = Envelope(from_="identity@example.com").from_()
238
245
  a == "identity@example.com", a.host == "example.com"
@@ -240,18 +247,18 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
240
247
  * **to**: E-mail or more in an iterable. When encrypting, we use keys of these identities. Multiple addresses may be given in a string, delimited by a comma (or semicolon). (The same is valid for `to`, `cc`, `bcc` and `reply-to`.)
241
248
  * **--to**: One or more e-mail addresses. Empty to read.
242
249
  ```bash
243
- $ envelope --to first@example.com second@example.com --message "hello"
250
+ $ envelope --to first@example.com second@example.com --message "hello"
244
251
  $ envelope --to
245
252
  first@example.com
246
253
  second@example.com
247
- ```
248
- * **.to(email_or_more)**: If None, current list of [Addresses](#address) returned. If False or "", current list is cleared.
254
+ ```
255
+ * **.to(email_or_more)**: If None, current list of [Addresses](#address) returned. If False or "", current list is cleared.
249
256
  ```python3
250
257
  Envelope()
251
258
  .to("person1@example.com")
252
259
  .to("person1@example.com, John <person2@example.com>")
253
260
  .to(["person3@example.com"])
254
- .to() # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
261
+ .to() # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
255
262
  ```
256
263
  * **Envelope(to=)**: E-mail or more in an iterable.
257
264
  * **cc**: E-mail or more in an iterable. Multiple addresses may be given in a string, delimited by a comma (or semicolon). (The same is valid for `to`, `cc`, `bcc` and `reply-to`.)
@@ -262,7 +269,7 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
262
269
  .cc("person1@example.com")
263
270
  .cc("person1@example.com, John <person2@example.com>")
264
271
  .cc(["person3@example.com"])
265
- .cc() # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
272
+ .cc() # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
266
273
  ```
267
274
  * **Envelope(cc=)**
268
275
  * **bcc**: E-mail or more in an iterable. Multiple addresses may be given in a string, delimited by a comma (or semicolon). (The same is valid for `to`, `cc`, `bcc` and `reply-to`.) The header is not sent.
@@ -277,7 +284,7 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
277
284
  * **--from-addr**: E-mail address or empty to read value.
278
285
  * **.from_addr(email)**: E-mail or False. If None, current `SMTP envelope MAIL FROM` returned as an [Address](#address) object (even an empty one).
279
286
  * **.Envelope(from_addr=)**
280
-
287
+
281
288
  ### Sending
282
289
  * **send**: Send the message to the recipients by e-mail. True (blank in *CLI*) to send now or False to print out debug information.
283
290
  * **--send**
@@ -285,12 +292,12 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
285
292
  * *send*: True to send now. False (or 0/false/no in *CLI*) to print debug information.
286
293
  * Returns the object back which converted to bool returns True if the message has been sent successfully.
287
294
  * **Envelope(send=)**
288
-
295
+
289
296
  ```bash
290
297
  $ envelope --to "user@example.org" --message "Hello world" --send 0
291
298
  ****************************************************************************************************
292
299
  Have not been sent from - to user@example.org
293
-
300
+
294
301
  Content-Type: text/html; charset="utf-8"
295
302
  Content-Transfer-Encoding: 7bit
296
303
  MIME-Version: 1.0
@@ -299,14 +306,14 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
299
306
  To: user@example.org
300
307
  Date: Mon, 07 Oct 2019 16:13:37 +0200
301
308
  Message-ID: <157045761791.29779.5279828659897745855@...>
302
-
309
+
303
310
  Hello world
304
311
  ```
305
312
  * **subject**: Mail subject. Gets encrypted with GPG, stays visible with S/MIME.
306
313
  * **--subject**
307
314
  * **.subject(text=None, encrypt=None)**:
308
315
  * `text` Subject text.
309
- * `encrypt` Text used instead of the real protected subject while PGP encrypting. False to not encrypt.
316
+ * `encrypt` Text used instead of the real protected subject while PGP encrypting. False to not encrypt.
310
317
  * If neither parameter specified, current subject returned.
311
318
  * **Envelope(subject=)**
312
319
  * **Envelope(subject_encrypted=)**
@@ -314,7 +321,7 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
314
321
  * **.date(date)** `str|False` Specify Date header (otherwise Date is added automatically). If False, the Date header will not be added automatically.
315
322
  * **smtp**: SMTP server
316
323
  * **--smtp**
317
- * **.smtp(host="localhost", port=25, user=, password=, security=, timeout=3, attempts=3, delay=3)**
324
+ * **.smtp(host="localhost", port=25, user=, password=, security=, timeout=3, attempts=3, delay=3, local_hostname=None)**
318
325
  * **Envelope(smtp=)**
319
326
  * Parameters:
320
327
  * `host` May include hostname or any of the following input formats (ex: path to an INI file or a `dict`)
@@ -322,10 +329,11 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
322
329
  * `timeout` How many seconds should SMTP wait before timing out.
323
330
  * `attempts` How many times we try to send the message to an SMTP server.
324
331
  * `delay` How many seconds to sleep before re-trying a timed out connection.
332
+ * `local_hostname` FQDN of the local host in the HELO/EHLO command.
325
333
  * Input format may be in the following form:
326
334
  * `None` default localhost server used
327
- * `smtplib.SMTP` object
328
- * `list` or `tuple` having `host, [port, [username, password, [security, [timeout, [attempts, [delay]]]]]]` parameters
335
+ * standard [`smtplib.SMTP`](https://docs.python.org/3/library/smtplib.html) object
336
+ * `list` or `tuple` having `host, [port, [username, password, [security, [timeout, [attempts, [delay, [local_hostname]]]]]]]` parameters
329
337
  * ex: `envelope --smtp localhost 125 me@example.com` will set up host, port and username parameters
330
338
  * `dict` specifying {"host": ..., "port": ...}
331
339
  * ex: `envelope --smtp '{"host": "localhost"}'` will set up host parameter
@@ -333,45 +341,45 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
333
341
  ```ini
334
342
  [SMTP]
335
343
  host = example.com
336
- port = 587
344
+ port = 587
337
345
  ```
338
346
  * Do not fear to pass the `smtp` in a loop, we make just a single connection to the server. If timed out, we attempt to reconnect once.
339
347
  ```python3
340
- smtp = localhost, 25
348
+ smtp = "localhost", 25
341
349
  for mail in mails:
342
350
  Envelope(...).smtp(smtp).send()
343
351
  ```
344
352
  * **attachments**
345
353
  * **--attach**: Path to the attachment, followed by optional file name to be used and/or mime type. This parameter may be used multiple times.
346
354
  ```bash
347
- envelope --attachment "/tmp/file.txt" "displayed-name.txt" "text/plain" --attachment "/tmp/another-file.txt"
355
+ envelope --attach "/tmp/file.txt" "displayed-name.txt" "text/plain" --attach "/tmp/another-file.txt"
348
356
  ```
349
357
  * **.attach(attachment=, mimetype=, name=, path=, inline=)**:
358
+ ```python3
359
+ Envelope().attach(path="/tmp/file.txt").attach(path="/tmp/another-file.txt")
360
+ ```
350
361
  * Three different usages when specifying contents:
351
362
  * **.attach(attachment=, mimetype=, name=)**: You can put [any attainable contents](#any-attainable-contents) of a single attachment into *attachment* and optionally add mime type or displayed file name.
352
363
  * **.attach(mimetype=, name=, path=)**: You can specify path and optionally mime type or displayed file name.
353
364
  * **.attach(attachment=)**: You can put a list of attachments. The list may contain tuples: `contents [,mime type] [,file name] [, True for inline]`.
354
- ```python3
355
- Envelope().attach(path="/tmp/file.txt").attach(path="/tmp/another-file.txt")
356
- ```
357
365
  * **.attach(inline=True|str)**: Specify content-id (CID) to reference the image from within HTML message body.
358
366
  * True: Filename or attachment or path file name is set as CID.
359
367
  * str: The attachment will get this CID.
360
- ```python3
361
- Envelope().attach("file.jpg", inline=True) # <img src='cid:file.jpg' />
368
+ ```python3
369
+ from pathlib import Path
370
+ Envelope().attach(Path("file.jpg"), inline=True) # <img src='cid:file.jpg' />
362
371
  Envelope().attach(b"GIF89a\x03\x00\x03...", name="file.gif", inline=True) # <img src='cid:file.gif' />
363
- Envelope().attach("file.jpg", inline="foo") # <img src='cid:foo' />
364
-
372
+ Envelope().attach(Path("file.jpg"), inline="foo") # <img src='cid:foo' />
373
+
365
374
  # Reference it like: .message("Hey, this is an inline image: <img src='cid:foo' />")
366
375
  ```
367
-
368
376
  * **Envelope(attachments=)**: Attachment or their list. Attachment is defined by [any attainable contents](#any-attainable-contents), optionally in tuple with the file name to be used in the e-mail and/or mime type and/or True for being inline: `contents [,mime type] [,file name] [, True for inline]`
369
377
  ```python3
370
378
  Envelope(attachments=[(Path("/tmp/file.txt"), "displayed-name.txt", "text/plain"), Path("/tmp/another-file.txt")])
371
- ```
372
- * **mime**: Sets contents mime subtype: "**auto**" (default), "**html**" or "**plain**" for plain text.
373
- Maintype is always set to "text".
374
- Set maintype to "text". If a line is longer than 1000 characters, makes the message be transferred safely by bytes (otherwise these non-standard long lines might cause a transferring SMTP server to include line breaks and redundant spaces that might break up ex: DKIM signature).
379
+ ```
380
+ * **mime**: Sets contents mime subtype: "**auto**" (default), "**html**" or "**plain**" for plain text.
381
+ Maintype is always set to "text".
382
+ If a line is longer than 1000 characters, makes the message be transferred safely by bytes (otherwise these non-standard long lines might cause a transferring SMTP server to include line breaks and redundant spaces that might break up ex: DKIM signature).
375
383
  In case of `Content-Type` header put to the message, **mime** section functionality **is skipped**.
376
384
  * **--mime SUBTYPE**
377
385
  * **.mime(subtype="auto", nl2br="auto")**
@@ -389,19 +397,19 @@ Whenever any attainable contents is mentioned, we mean plain **text**, **bytes**
389
397
  .header("Generic-Header") # ["1", "2"]
390
398
  ```
391
399
  * **Envelope(headers=[(name, value)])**
392
-
393
- Equivalent headers:
400
+
401
+ Equivalent headers:
394
402
  ```bash
395
403
  envelope --header X-Mailer my-app
396
404
  ```
397
-
405
+
398
406
  ```python3
399
407
  Envelope(headers=[("X-Mailer", "my-app")])
400
408
  Envelope().header("X-Mailer", "my-app")
401
- ```
409
+ ```
402
410
  #### Specific headers
403
411
  These helpers are available via fluent interface.
404
-
412
+
405
413
  * **.list_unsubscribe(uri=None, one_click=False, web=None, email=None)**: You can specify either url, email or both.
406
414
  * **.list_unsubscribe(uri)**: We try to determine whether this is e-mail and prepend brackets and 'https:'/'mailto:' if needed. Ex: `me@example.com?subject=unsubscribe`, `example.com/unsubscribe`, `<https://example.com/unsubscribe>`
407
415
  * **.list_unsubscribe(email=)**: E-mail address. Ex: `me@example.com`, `mailto:me@example.com`
@@ -413,21 +421,21 @@ These helpers are available via fluent interface.
413
421
  Envelope().list_unsubscribe("example.com/unsubscribe")
414
422
  Envelope().list_unsubscribe(web="example.com/unsubscribe")
415
423
  Envelope().list_unsubscribe("<https://example.com/unsubscribe>")
416
-
424
+
417
425
  # This will produce:
418
426
  # List-Unsubscribe: <https://example.com/unsubscribe>, <mailto:me@example.com?subject=unsubscribe>
419
427
  Envelope().list_unsubscribe("example.com/unsubscribe", mail="me@example.com?subject=unsubscribe")
420
- ```
421
-
422
- * **.auto_submitted**:
423
- * **.auto_submitted(val="auto-replied")**: Direct response to another message by an automatic process.
428
+ ```
429
+
430
+ * **.auto_submitted**:
431
+ * **.auto_submitted(val="auto-replied")**: Direct response to another message by an automatic process.
424
432
  * **.auto_submitted.auto_generated()**: automatic (often periodic) processes (such as UNIX "cron jobs") which are not direct responses to other messages
425
433
  * **.auto_submitted.no()**: message was originated by a human
426
434
 
427
435
  ```python3
428
- Envelope().auto_submitted() # mark message as automatic
436
+ Envelope().auto_submitted() # mark message as automatic
429
437
  Envelope().auto_submitted.no() # mark message as human produced
430
- ```
438
+ ```
431
439
 
432
440
  ### Cipher standard method
433
441
  Note that if neither *gpg* nor *smime* is specified, we try to determine the method automatically.
@@ -442,13 +450,13 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
442
450
  ### Signing
443
451
  * **sign**: Sign the message.
444
452
  * **`key`** parameter
445
- * GPG:
453
+ * GPG:
446
454
  * Blank (*CLI*) or True (*module*) for user default key
447
455
  * "auto" for turning on signing if there is a key matching to the "from" header
448
456
  * key ID/fingerprint
449
457
  * e-mail address of the identity whose key is to be signed with
450
458
  * [Any attainable contents](#any-attainable-contents) with the key to be signed with (will be imported into keyring)
451
- * S/MIME: [Any attainable contents](#any-attainable-contents) with key to be signed with. May contain signing certificate as well.
459
+ * S/MIME: [Any attainable contents](#any-attainable-contents) with key to be signed with. May contain signing certificate as well.
452
460
  * **--sign key**: (for `key` see above)
453
461
  * **--sign-path**: Filename with the From\'s private key. (Alternative to the `sign` parameter.)
454
462
  * **--passphrase**: Passphrase to the key if needed.
@@ -462,10 +470,10 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
462
470
  * **Envelope(attach_key=)**: If true, append GPG public key as an attachment when sending.
463
471
  * **Envelope(cert=)**: S/MIME: [Any attainable contents](#any-attainable-contents)
464
472
  ### Encrypting
465
- * **encrypt**: Recipient GPG public key or S/MIME certificate to be encrypted with.
473
+ * **encrypt**: Recipient GPG public key or S/MIME certificate to be encrypted with.
466
474
  * **`key`** parameter
467
475
  * GPG:
468
- * Blank (*CLI*) or True (*module*) to force encrypt with the user default keys (identities in the "from", "to", "cc" and "bcc" headers)
476
+ * Blank (*CLI*) or True (*module*) to force encrypt with the user default keys (identities in the "from", "to", "cc" and "bcc" headers)
469
477
  * "auto" for turning on encrypting if there is a matching key for every recipient
470
478
  * key ID/fingerprint
471
479
  * e-mail address of the identity whose key is to be encrypted with
@@ -477,19 +485,19 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
477
485
  * **.encrypt(key=True, sign=, key_path=)**:
478
486
  * **`sign`** See signing, ex: you may specify boolean or default signing key ID/fingerprint or "auto" for GPG or [any attainable contents](#any-attainable-contents) with an S/MIME key + signing certificate.
479
487
  * **`key_path`**: Key/certificate contents (alternative to the `key` parameter)
480
- * **.encryption(key=True, key_path=)**: Encrypt later (when launched with *.sign()*, *.encrypt()* or *.send()* functions. If needed, in the parameters specify [any attainable contents](#any-attainable-contents) with GPG encryption key or S/MIME encryption certificate.
488
+ * **.encryption(key=True, key_path=)**: Encrypt later (when launched with *.sign()*, *.encrypt()* or *.send()* functions. If needed, in the parameters specify [any attainable contents](#any-attainable-contents) with GPG encryption key or S/MIME encryption certificate.
481
489
  * **Envelope(encrypt=key)**: (for `key` see above)
482
490
  ```bash
483
491
  # message gets encrypted for multiple S/MIME certificates
484
492
  envelope --smime --encrypt-path recipient1.pem recipient2.pem --message "Hello"
485
-
493
+
486
494
  # message gets encrypted with the default GPG key
487
495
  envelope --message "Encrypted GPG message!" --subject "Secret subject will not be shown" --encrypt --from person@example.com --to person@example.com
488
-
496
+
489
497
  # message not encrypted for the sender (from Bash)
490
498
  envelope --message "Encrypted GPG message!" --subject "Secret subject will not be shown" --encrypt receiver@example.com receiver2@example.com --from person@example.com --to receiver@example.com receiver2@example.com
491
499
  ```
492
-
500
+
493
501
  ```python3
494
502
  # message not encrypted for the sender (from Python)
495
503
  Envelope()
@@ -497,11 +505,11 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
497
505
  .subject("Secret subject will not be shown")
498
506
  .from_("person@example.com")
499
507
  .to(("receiver@example.com", "receiver2@example.com"))
500
- .encrypt(("receiver@example.com", "receiver2@example.com"))
508
+ .encrypt(("receiver@example.com", "receiver2@example.com"))
501
509
  ```
502
510
 
503
511
  #### GPG notes
504
- * If the GPG encryption fails, it tries to determine which recipient misses the key.
512
+ * If the GPG encryption fails, it tries to determine which recipient misses the key.
505
513
  * By default, GPG encrypts with the key of the **from** header recipient too.
506
514
  * Key ID/fingerprint is internally ignored right now, GPG decides itself which key is to be used.
507
515
 
@@ -512,19 +520,19 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
512
520
  * **--attachments [NAME]** Get the list of attachments or a contents of the one specified by `NAME`
513
521
  * **.attachments(name=None, inline=None)**
514
522
  * **name** (str): The name of the only desired attachment to be returned.
515
- * **inline** (bool): Filter inline/enclosed attachments only.
523
+ * **inline** (bool): Filter inline/enclosed attachments only.
516
524
  * *Attachment* object has the attributes *.name* file name, *.mimetype*, *.data* raw data
517
525
  * if casted to *str*/*bytes*, its raw *.data* are returned
518
- * **.copy()**: Return deep copy of the instance to be used independently.
519
- ```python3
526
+ * **.copy()**: Return deep copy of the instance to be used independently.
527
+ ```python3
520
528
  factory = Envelope().cc("original@example.com").copy
521
529
  e1 = factory().to("to-1@example.com")
522
- e2 = factory().to("to-2@example.com").cc("additional@example.com") #
530
+ e2 = factory().to("to-2@example.com").cc("additional@example.com") #
523
531
 
524
532
  print(e1.recipients()) # {'to-1@example.com', 'original@example.com'}
525
533
  print(e2.recipients()) # {'to-2@example.com', 'original@example.com', 'additional@example.com'}
526
534
  ```
527
- * Read message and subject by **.message()** and **.subject()**
535
+ * Read message and subject by **.message()** and **.subject()**
528
536
  * **preview**: Returns the string of the message or data as a human-readable text.
529
537
  Ex: whilst we have to use quoted-printable (as seen in __str__), here the output will be plain text.
530
538
  * **--preview**
@@ -532,11 +540,11 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
532
540
  * **check**: Check all e-mail addresses and SMTP connection and return True/False if succeeded. Tries to find SPF, DKIM and DMARC DNS records depending on the From's domain and print them out.
533
541
  * **--check**
534
542
  * **.check(check_mx=True, check_smtp=True)**
535
- * `check_mx` E-mail addresses can be checked for MX record, not only for their format.
543
+ * `check_mx` E-mail addresses can be checked for MX record, not only for their format.
536
544
  * `check_smtp` We try to connect to the SMTP host.
537
-
545
+
538
546
  ```bash
539
- $ envelope --smtp localhost 25 --from me@example.com --check
547
+ $ envelope --smtp localhost 25 --from me@example.com --check
540
548
  SPF found on the domain example.com: v=spf1 -all
541
549
  See: dig -t SPF example.com && dig -t TXT example.com
542
550
  DKIM found: ['v=DKIM1; g=*; k=rsa; p=...']
@@ -547,12 +555,12 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
547
555
  * **.as_message()**: Generates an email.message.Message object.
548
556
  ```python3
549
557
  e = Envelope("hello").as_message()
550
- print(type(e), e.get_payload()) # <class 'email.message.EmailMessage'> hello\n
558
+ print(type(e), e.get_payload()) # <class 'email.message.EmailMessage'> hello\n
551
559
  ```
552
560
  Note: due to a bug in a standard Python library https://github.com/python/cpython/issues/99533 and #19 you void GPG when you access the message this way wihle signing an attachment with a name longer than 34 chars.
553
561
  * **load**: Parse [any attainable contents](#any-attainable-contents) (including email.message.Message) like an EML file to build an Envelope object.
554
562
  * It can decrypt the message and parse its (inline or enclosed) attachments.
555
- * Note that if you will send this reconstructed message, you might not probably receive it due to the Message-ID duplication. Delete at least Message-ID header prior to re-sending.
563
+ * Note that if you will send this reconstructed message, you might not probably receive it due to the Message-ID duplication. Delete at least Message-ID header prior to re-sending.
556
564
  * (*static*) **.load(message, \*, path=None, key=None, cert=None, gnupg_home=None)**
557
565
  * **message**: [Any attainable contents](#any-attainable-contents)
558
566
  * **path**: Path to the file, alternative to the `message`
@@ -562,7 +570,7 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
562
570
  Envelope.load("Subject: testing message").subject() # "testing message"
563
571
  ```
564
572
  * bash
565
- * allows use blank `--subject` or `--message` flags to display the
573
+ * allows use blank `--subject` or `--message` flags to display the
566
574
  * **--load FILE**
567
575
  ```bash
568
576
  $ envelope --load email.eml
@@ -570,22 +578,22 @@ Note that if neither *gpg* nor *smime* is specified, we try to determine the met
570
578
  Content-Transfer-Encoding: 7bit
571
579
  MIME-Version: 1.0
572
580
  Subject: testing message
573
-
581
+
574
582
  Message body
575
-
583
+
576
584
  $ envelope --load email.eml --subject
577
- testing message
585
+ testing message
578
586
  ```
579
- * (*bash*) piped in content, envelope executable used with no argument
587
+ * (*bash*) piped in content, envelope executable used with no argument
580
588
  ```bash
581
589
  $ echo "Subject: testing message" | envelope
582
590
  Content-Type: text/plain; charset="utf-8"
583
591
  Content-Transfer-Encoding: 7bit
584
592
  MIME-Version: 1.0
585
593
  Subject: testing message
586
-
594
+
587
595
  $ cat email.eml | envelope
588
-
596
+
589
597
  $ envelope < email.eml
590
598
  ```
591
599
  * **smtp_quit()**: As Envelope tends to re-use all the SMTP instances, you may want to quit them explicitly. Either call this method to the Envelope class to close all the cached connections or to an Envelope object to close only the connection it currently uses.
@@ -613,7 +621,7 @@ Empty object works too. For example, if the `From` header is not set, we get an
613
621
  a = Envelope.load("Empty message").from_()
614
622
  bool(a) is False, a.host == ""
615
623
  Address() == Address("") == "", Address().address == ""
616
- ```
624
+ ```
617
625
 
618
626
  Method `.casefold()` returns casefolded `Address` object which is useful for comparing with strings whereas comparing with other `Address` object casefolds automatically
619
627
  ```python3
@@ -626,7 +634,7 @@ Method `.is_valid(check_mx=False)` returns boolean if the format is valid. When
626
634
 
627
635
  Since the `Address` is a subclass of `str`, you can safely join such objects.
628
636
 
629
- ```python3
637
+ ```python3
630
638
  ", ".join([a, a]) # "John <person@example.com>, "John <person@example.com>"
631
639
  a + " hello" # "John <person@example.com> hello"
632
640
  ```
@@ -638,7 +646,7 @@ Address object is equal to a string if the string contains its e-mail address or
638
646
  "person@example.com" == Address("John <person@example.com>") == "John <person@example.com>" # True
639
647
  ```
640
648
 
641
- Concerning `to`, `cc`, `bcc` and `reply-to`, multiple addresses may always be given in a string, delimited by comma (or semicolon). The `.get(address:bool, name:bool)` method may be called on an `Address` object to filter the desired information.
649
+ Concerning `to`, `cc`, `bcc` and `reply-to`, multiple addresses may always be given in a string, delimited by comma (or semicolon). The `.get(address:bool, name:bool)` method may be called on an `Address` object to filter the desired information.
642
650
  ```python3
643
651
  e = (Envelope()
644
652
  .to("person1@example.com")
@@ -725,7 +733,7 @@ with open("/tmp/message.txt") as f:
725
733
  ```
726
734
 
727
735
  Sign and encrypt the message so that's decryptable by keys for me@example.com and remote_person@example.com (that should already be loaded in the keyring).
728
- ```python3
736
+ ```python3
729
737
  Envelope(message="Hello world", sign=True,
730
738
  encrypt=True,
731
739
  from_="me@example.com",
@@ -733,7 +741,7 @@ Envelope(message="Hello world", sign=True,
733
741
  ```
734
742
 
735
743
  Sign and encrypt the message so that's decryptable by keys for me@example.com and remote_person@example.com (that get's imported to the keyring from the file).
736
- ```python3
744
+ ```python3
737
745
  Envelope(message="Hello world", sign=True,
738
746
  encrypt=Path("/tmp/remote_key.asc"),
739
747
  from_="me@example.com",
@@ -746,12 +754,12 @@ Envelope(message="Hello world", sign=True, gnupg="/tmp/my-keyring/")
746
754
  ```
747
755
 
748
756
  Sign the message with a key that needs passphrase.
749
- ```python3
757
+ ```python3
750
758
  Envelope(message="Hello world", sign=True, passphrase="my-password")
751
759
  ```
752
760
 
753
- Sign a message with signing by default turned previously on and having a default keyring path. Every `factory` call will honour these defaults.
754
- ```python3
761
+ Sign a message with signing by default turned previously on and having a default keyring path. Every `factory` call will honour these defaults.
762
+ ```python3
755
763
  factory = Envelope().signature(True).gpg("/tmp/my-keyring").copy
756
764
  factory().(message="Hello world")
757
765
  ```
@@ -770,17 +778,17 @@ envelope --to "user@example.org" --message "Hello world" --send
770
778
  Send while having specified the SMTP server host, port, username, password.
771
779
 
772
780
  ```bash
773
- envelope --to "user@example.org" message "Hello world" --send --smtp localhost 123 username password
781
+ envelope --to "user@example.org" message "Hello world" --send --smtp localhost 123 username password
774
782
  ```
775
783
 
776
784
  Send while having specified the SMTP server through a dictionary.
777
785
  ```bash
778
- envelope --to "user@example.org" --message "Hello world" --send --smtp '{"host": "localhost", "port": "123"}'
786
+ envelope --to "user@example.org" --message "Hello world" --send --smtp '{"host": "localhost", "port": "123"}'
779
787
  ```
780
788
 
781
789
  Send while having specified the SMTP server via module call.
782
790
  ```python3
783
- Envelope(message="Hello world", to="user@example.org", send=True, smtp={"host":"localhost"})
791
+ Envelope(message="Hello world", to="user@example.org", send=True, smtp={"host":"localhost"})
784
792
  ```
785
793
 
786
794
  ## Attachment
@@ -790,15 +798,15 @@ Envelope(attachment=Path("/tmp/file.txt")) # file name will be 'file.txt'
790
798
 
791
799
  with open("/tmp/file.txt") as f:
792
800
  Envelope(attachment=f) # file name will be 'file.txt'
793
-
801
+
794
802
  with open("/tmp/file.txt") as f:
795
803
  Envelope(attachment=(f, "filename.txt"))
796
-
804
+
797
805
  Envelope().attach(path="/tmp/file.txt", name="filename.txt")
798
806
  ```
799
807
 
800
808
  ## Inline images
801
- The only thing you have to do is to set the `inline=True` parameter of the attachment. Then, you can reference the image from within your message, with the help of `cid` keyword. For more details, see *attachments* in the [Sending](#sending) section.
809
+ The only thing you have to do is to set the `inline=True` parameter of the attachment. Then, you can reference the image from within your message, with the help of `cid` keyword. For more details, see *attachments* in the [Sending](#sending) section.
802
810
  ```python3
803
811
  (Envelope()
804
812
  .attach(path="/tmp/file.jpg", inline=True)
@@ -859,7 +867,7 @@ RQ8QtLLEza+rs+1lgcPgdBZEHFpYpgDb0AUvYg9d
859
867
  ```
860
868
 
861
869
  # Related affairs
862
- Sending an e-mail does not mean it will be received. Sending it successfully through your local domain does not mean a public mailbox will accept it as well. If you are not trustworthy enough, your e-mail may not even appear at the recipient's spam bin, it can just be discarded without notice.
870
+ Sending an e-mail does not mean it will be received. Sending it successfully through your local domain does not mean a public mailbox will accept it as well. If you are not trustworthy enough, your e-mail may not even appear at the recipient's spam bin, it can just be discarded without notice.
863
871
 
864
872
  ## Configure your SMTP
865
873
  It is always easier if you have an account on an SMTP server the application is able to send e-mails with. If it is not the case, various SMTP server exist but as a quick and non-secure solution, I've tested [bytemark/smtp](https://hub.docker.com/r/bytemark/smtp/) docker image that allows you to start up a SMTP server by a single line.
@@ -884,18 +892,18 @@ GNUPGHOME=/var/www/.gnupg sudo -H -u www-data gpg --export [APPLICATION_EMAIL] |
884
892
  GNUPGHOME=/var/www/.gnupg sudo -H -u www-data envelope --message "Hello world" --subject "GPG signing test" --sign [key ID] --from [application e-mail] --to [your e-mail] --send # you now receive e-mail and may import the key and set the trust to the key
885
893
  ```
886
894
 
887
- It takes few hours to a key to propagate. If the key cannot be imported in your e-mail client because not found on the servers, try in the morning again or check the online search form at http://hkps.pool.sks-keyservers.net.
895
+ It takes few hours to a key to propagate. If the key cannot be imported in your e-mail client because not found on the servers, try in the morning again or check the online search form at http://hkps.pool.sks-keyservers.net.
888
896
  Put your fingerprint on the web or on the business card then so that everybody can check your signature is valid.
889
897
 
890
898
  ### Configure your S/MIME
891
899
  If you are supposed to use S/MIME, you would probably be told where to take your key and certificate from. If planning to try it all by yourself, generate your `certificate.pem`.
892
-
900
+
893
901
  * Either: Do you have private key?
894
902
  ```bash
895
903
  openssl req -key YOUR-KEY.pem -nodes -x509 -days 365 -out certificate.pem # will generate privkey.pem alongside
896
904
  ```
897
-
898
- * Or: Do not you have private key?
905
+
906
+ * Or: Do not you have private key?
899
907
  ```bash
900
908
  openssl req -newkey rsa:1024 -nodes -x509 -days 365 -out certificate.pem # will generate privkey.pem alongside
901
909
  ```
@@ -908,17 +916,17 @@ envelope --message "Hello world" --subject "S/MIME signing test" --sign-path [ke
908
916
  ## DNS validation tools
909
917
  This is just a short explanation on these anti-spam mechanisms so that you can take basic notion what is going on.
910
918
 
911
- Every time, the receiver should ask the From's domain these questions over DNS.
919
+ Every time, the receiver should ask the From's domain these questions over DNS.
912
920
 
913
921
  ### SPF
914
- The receiver asks the sender's domain: Do you allow the senders IP/domain to send the e-mail on your behalf? Is the IP/domain the mail originates from enlisted as valid in the DNS of the SMTP envelope MAIL FROM address domain?
922
+ The receiver asks the sender's domain: Do you allow the senders IP/domain to send the e-mail on your behalf? Is the IP/domain the mail originates from enlisted as valid in the DNS of the SMTP envelope MAIL FROM address domain?
915
923
 
916
924
  Check your domain on SPF:
917
925
  ```bash
918
926
  dig -t TXT example.com
919
927
  ```
920
928
 
921
- SPF technology is tied to the SMTP envelope MAIL FROM address which is specified with the `.from_addr` method and then stored into the Return-Path header by the receiving server, and it has nothing in common with the headers like From `.from_`, Reply-To `.reply_to`, or Sender `.header("Sender")`.
929
+ SPF technology is tied to the SMTP envelope MAIL FROM address which is specified with the `.from_addr` method and then stored into the Return-Path header by the receiving server, and it has nothing in common with the headers like From `.from_`, Reply-To `.reply_to`, or Sender `.header("Sender")`.
922
930
 
923
931
  ### DKIM
924
932
  The receiver asks the sender's domain: Give me the public key so that I may check the hash in the e-mail header that assert the message was composed by your private key. So that the e-mail comes trustworthy from you and nobody modified it on the way.
@@ -926,7 +934,7 @@ The receiver asks the sender's domain: Give me the public key so that I may chec
926
934
  Check your domain on DKIM:
927
935
  ```bash
928
936
  dig -t TXT [selector]._domainkey.example.com
929
- ```
937
+ ```
930
938
  You can obtain the `selector` from an e-mail message you received. Check the line `DKIM-Signature` and the value of the param `s`.
931
939
  ```
932
940
  DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; s=default;
@@ -938,4 +946,4 @@ What is your policy concerning SPF and DKIM? What abuse address do you have?
938
946
  Check your domain on DMARC:
939
947
  ```bash
940
948
  dig -t TXT _dmarc.example.com
941
- ```
949
+ ```