md2plain
Markdown in, plain-text email out.
md2plain renders Markdown as a plain-text email and copies it to the macOS
clipboard as text/plain. Paste it into Gmail's Plain text mode and the
message is sent as a real text/plain message: hard-wrapped, with no HTML,
no fonts, and no client-side reflow.
Demo
Input:
# Quarterly Update
This is a **short** note with a [link](https://example.com).
* one
* two
> quoted
Output:
Quarterly Update
================
This is a short note with a link (https://example.com).
* one
* two
> quoted
Why
- Gmail's normal compose always sends HTML, even when you paste plain text.
- Rich text copied from Typora carries its own CSS, which mail clients mangle.
- Plain text has no automatic reflow, so the sender has to control line width.
- A 75-column limit leaves room for
>quote prefixes and stays within RFC 5322's recommended 78 characters.
Requirements
- macOS (the clipboard is written through AppKit)
- Python 3.10 or newer
Install
With pipx:
pipx install .
Or from a checkout, in a virtual environment:
python3 -m venv .venv
.venv/bin/pip install -e .
Usage
md2plain note.md
Then in Gmail: open the compose window, click the three-dot menu, choose
Plain text mode, and press Cmd+V.
To verify, send a message to yourself and use Show original. The message should contain only:
Content-Type: text/plain; charset="UTF-8"
No multipart/alternative, no text/html.
Options
| Option | Description |
|---|---|
--width N |
Line width in columns (default: 75) |
--watch |
Update the clipboard whenever the file is saved |
--print |
Write to stdout instead of the clipboard |
-o, --output FILE |
Also write the plain text to a file |
--soft-breaks |
Treat single newlines as spaces |
--version |
Print the version and exit |
Output format
| Markdown | Output |
|---|---|
# Heading |
heading + ==== underline |
## Heading |
heading + ---- underline |
### Heading |
heading + ~~~~ underline |
| Paragraph | hard-wrapped at 75 columns (CJK counts as two) |
- item |
* item, with hanging indent |
1. item |
1. item |
> quote |
> quote |
| Code block | indented by four spaces |
[text](url) |
text (url) |
| Table | column-aligned plain text |
| Single newline | kept (signatures and dates stay on their own lines) |
How it works
markdown-it-pyparses CommonMark plus tables, strikethrough, and autolinks.src/md2plain/render.pywraps text by display width (CJK counts as two columns), renders headings, lists, quotes, and tables.src/md2plain/cli.pyreads UTF-8 (BOM tolerant) and writes exactly one clipboard type:text/plain.
Limitations
- macOS only; the clipboard backend is AppKit.
- Plain text has no bold, italics, or clickable links. Link URLs are kept as text, and links inside headings or table cells lose the URL.
- Images become alt text. YAML front matter, footnotes, math, and Mermaid diagrams are not converted.
- Tables are aligned with spaces, so proportional fonts break the alignment.
- Gmail may collapse
>quote blocks behind a three-dot control; this is display only and the content stays intact.
Development
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m unittest discover -s tests -v
.venv/bin/ruff check src tests
.venv/bin/black --check src tests
The ./md2plain script in the repository root is a development launcher; it
runs the package from .venv. Installed users get the md2plain command from
pyproject.toml.
License
MIT. See CHANGELOG.md for release history.
Metadata
Release files for md2plain 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| md2plain-0.1.0.tar.gz | 12.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| md2plain-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.7 kB
Release files / md2plain-0.1.0.tar.gz
| Download URL | md2plain-0.1.0.tar.gz |
|---|---|
| Size | 12.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2f05f94696ee3331683b6485b221243e8d27b88625c9f0b4324c0301ba9ba472
|
|
BLAKE2b-256 checksum How to use checksums |
5d6e06e01aac4828056d6d685eca173b09a5ac861bd633965016157d2bc6fb82
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / md2plain-0.1.0-py3-none-any.whl
| Download URL | md2plain-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8966f6abdd98c3fc76de4b61fab3b28069c8a8987aa323e0e9ce15e7a53a7944
|
|
BLAKE2b-256 checksum How to use checksums |
f3f144f3967cec0e2555049e0c7306b25fb8867eee126f72ecc7fbf77d4eb8fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for md2plain 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.