diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6115fc1..814cde2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Install the project run: uv sync --all-extras --dev - - name: Install emailer-lib + - name: Install nbmail run: uv pip install -e . # quarto docs build ---- @@ -57,7 +57,7 @@ jobs: with: source-dir: docs/_site - test-emailer-lib: + test-nbmail: runs-on: ubuntu-latest strategy: matrix: @@ -80,19 +80,19 @@ jobs: - name: Install the project run: uv sync --all-extras --dev - - name: Install emailer-lib + - name: Install nbmail run: uv pip install -e . - name: Install the project deps run: uv pip install -e .[dev] - - name: Test emailer-lib + - name: Test nbmail run: | - uv run pytest emailer_lib/tests/ --cov=emailer_lib --cov-report=xml --cov-report=term-missing + uv run pytest nbmail/tests/ --cov=nbmail --cov-report=xml --cov-report=term-missing - name: Upload coverage reports uses: codecov/codecov-action@v4 if: ${{ matrix.python-version == '3.12' }} with: - file: emailer-lib/coverage.xml - flags: emailer-lib + file: nbmail/coverage.xml + flags: nbmail diff --git a/Makefile b/Makefile index c0c4950..b743416 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,10 @@ preview: cd docs && quarto preview test: - pytest emailer_lib/tests emailer_lib/mjml/tests --cov-report=xml + pytest nbmail/tests nbmail/mjml/tests --cov-report=xml test-update: - pytest emailer_lib/tests emailer_lib/mjml/tests --snapshot-update + pytest nbmail/tests nbmail/mjml/tests --snapshot-update generate-mjml-tags: - python3 emailer_lib/mjml/scripts/generate_tags.py + python3 nbmail/mjml/scripts/generate_tags.py diff --git a/README.md b/README.md index b026ae7..2c21d00 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# emailer-lib +# nbmail @@ -11,12 +11,12 @@ -> ⚠️ **emailer-lib is currently in development, expect breaking changes.** +> ⚠️ **nbmail is currently in development, expect breaking changes.** -### What is [emailer-lib](https://posit-dev.github.io/email-for-data-science/reference/)? +### What is [nbmail](https://posit-dev.github.io/email-for-data-science/reference/)? -**emailer-lib** is a Python package for serializing, previewing, and sending email messages in a consistent, simple structure. It provides utilities to convert emails from different sources (Redmail, Yagmail, MJML, Quarto JSON) into a unified intermediate format, and send them via multiple backends (Gmail, SMTP, Mailgun, etc.). +**nbmail** is a Python package for serializing, previewing, and sending email messages in a consistent, simple structure. It provides utilities to convert emails from different sources (Redmail, Yagmail, MJML, Quarto JSON) into a unified intermediate format, and send them via multiple backends (Gmail, SMTP, Mailgun, etc.). The package is designed for data science workflows and Quarto projects, making it easy to generate, preview, and deliver rich email content programmatically. @@ -24,27 +24,27 @@ The package is designed for data science workflows and Quarto projects, making i Install the latest release from your local repo or PyPI: ```bash -pip install -e ./emailer-lib +pip install -e ./nbmail ``` --> ## Example Usage ```python -from emailer_lib import ( - quarto_json_to_intermediate_email, - IntermediateEmail, - send_intermediate_email_with_gmail, +from nbmail import ( + quarto_json_to_email, + Email, + send_email_with_gmail, ) # Read a Quarto email JSON file -email_struct = quarto_json_to_intermediate_email("email.json") +email_struct = quarto_json_to_email("email.json") # Preview the email as HTML email_struct.write_preview_email("preview.html") # Send the email via Gmail -send_intermediate_email_with_gmail("your_email@gmail.com", "your_password", email_struct) +send_email_with_gmail("your_email@gmail.com", "your_password", email_struct) ``` ## Features @@ -81,4 +81,4 @@ url = {https://github.com/posit-dev/gt-extras}, version = {0.0.1} } ``` --> -For more information, see the [docs](https://posit-dev.github.io/email-for-data-science/reference) or [open an issue](https://github.com/posit-dev/email-for-data-science/issues) with questions or suggestions! \ No newline at end of file +For more information, see the [docs](https://posit-dev.github.io/email-for-data-science/reference) or [open an issue](https://github.com/posit-dev/email-for-data-science/issues) with questions or suggestions! diff --git a/docs/_quarto.yml b/docs/_quarto.yml index cfa6598..6a56812 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -29,7 +29,7 @@ website: - orchestrating-tests.qmd quartodoc: - package: emailer_lib + package: nbmail dir: reference title: API Reference css: reference/_styles-quartodoc.css @@ -43,31 +43,31 @@ quartodoc: An email object that in a serializable, previewable format, optimized for emails with content generated by data scientists. contents: - - name: IntermediateEmail + - name: Email children: separate - - name: IntermediateEmail.write_preview_email - - name: IntermediateEmail.write_email_message - - name: IntermediateEmail.preview_send_email + - name: Email.write_preview_email + - name: Email.write_email_message + - name: Email.preview_send_email - title: Uploading emails desc: > - Converting emails to IntermediateEmails, + Converting emails to Emails, at which point they can be previewed, tested, and sent. contents: - - quarto_json_to_intermediate_email - - mjml_to_intermediate_email - - redmail_to_intermediate_email - - yagmail_to_intermediate_email + - quarto_json_to_email + - mjml_to_email + - redmail_to_email + - yagmail_to_email - title: Sending desc: > Functions to sending emails with different providers. And a special handy one to bypass the intermediate object if you are sending a quarto email. contents: - - send_intermediate_email_with_gmail - - send_intermediate_email_with_smtp - - send_intermediate_email_with_redmail - - send_intermediate_email_with_yagmail - - send_intermediate_email_with_mailgun + - send_email_with_gmail + - send_email_with_smtp + - send_email_with_redmail + - send_email_with_yagmail + - send_email_with_mailgun - send_quarto_email_with_gmail - title: Utilities @@ -79,7 +79,7 @@ quartodoc: - title: MJML Authoring desc: > Write responsive emails with MJML - package: emailer_lib + package: nbmail contents: - mjml.mjml - mjml.head diff --git a/docs/objects.json b/docs/objects.json index 6165209..073ffef 100644 --- a/docs/objects.json +++ b/docs/objects.json @@ -1 +1 @@ -{"project": "emailer_lib", "version": "0.0.9999", "count": 106, "items": [{"name": "emailer_lib.IntermediateEmail.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/emailer_lib.IntermediateEmail.preview_send_email.html#emailer_lib.IntermediateEmail.preview_send_email", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/emailer_lib.IntermediateEmail.preview_send_email.html#emailer_lib.IntermediateEmail.preview_send_email", "dispname": "emailer_lib.IntermediateEmail.preview_send_email"}, {"name": "emailer_lib.IntermediateEmail.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/emailer_lib.IntermediateEmail.write_email_message.html#emailer_lib.IntermediateEmail.write_email_message", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/emailer_lib.IntermediateEmail.write_email_message.html#emailer_lib.IntermediateEmail.write_email_message", "dispname": "emailer_lib.IntermediateEmail.write_email_message"}, {"name": "emailer_lib.IntermediateEmail.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/emailer_lib.IntermediateEmail.write_preview_email.html#emailer_lib.IntermediateEmail.write_preview_email", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/emailer_lib.IntermediateEmail.write_preview_email.html#emailer_lib.IntermediateEmail.write_preview_email", "dispname": "emailer_lib.IntermediateEmail.write_preview_email"}, {"name": "emailer_lib.IntermediateEmail", "domain": "py", "role": "class", "priority": "1", "uri": "reference/IntermediateEmail.html#emailer_lib.IntermediateEmail", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail", "domain": "py", "role": "class", "priority": "1", "uri": "reference/IntermediateEmail.html#emailer_lib.IntermediateEmail", "dispname": "emailer_lib.IntermediateEmail"}, {"name": "emailer_lib.IntermediateEmail.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/IntermediateEmail.write_preview_email.html#emailer_lib.IntermediateEmail.write_preview_email", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/IntermediateEmail.write_preview_email.html#emailer_lib.IntermediateEmail.write_preview_email", "dispname": "emailer_lib.IntermediateEmail.write_preview_email"}, {"name": "emailer_lib.IntermediateEmail.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/IntermediateEmail.write_email_message.html#emailer_lib.IntermediateEmail.write_email_message", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/IntermediateEmail.write_email_message.html#emailer_lib.IntermediateEmail.write_email_message", "dispname": "emailer_lib.IntermediateEmail.write_email_message"}, {"name": "emailer_lib.IntermediateEmail.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/IntermediateEmail.preview_send_email.html#emailer_lib.IntermediateEmail.preview_send_email", "dispname": "-"}, {"name": "emailer_lib.structs.IntermediateEmail.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/IntermediateEmail.preview_send_email.html#emailer_lib.IntermediateEmail.preview_send_email", "dispname": "emailer_lib.IntermediateEmail.preview_send_email"}, {"name": "emailer_lib.quarto_json_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/quarto_json_to_intermediate_email.html#emailer_lib.quarto_json_to_intermediate_email", "dispname": "-"}, {"name": "emailer_lib.ingress.quarto_json_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/quarto_json_to_intermediate_email.html#emailer_lib.quarto_json_to_intermediate_email", "dispname": "emailer_lib.quarto_json_to_intermediate_email"}, {"name": "emailer_lib.mjml_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml_to_intermediate_email.html#emailer_lib.mjml_to_intermediate_email", "dispname": "-"}, {"name": "emailer_lib.ingress.mjml_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml_to_intermediate_email.html#emailer_lib.mjml_to_intermediate_email", "dispname": "emailer_lib.mjml_to_intermediate_email"}, {"name": "emailer_lib.redmail_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/redmail_to_intermediate_email.html#emailer_lib.redmail_to_intermediate_email", "dispname": "-"}, {"name": "emailer_lib.ingress.redmail_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/redmail_to_intermediate_email.html#emailer_lib.redmail_to_intermediate_email", "dispname": "emailer_lib.redmail_to_intermediate_email"}, {"name": "emailer_lib.yagmail_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/yagmail_to_intermediate_email.html#emailer_lib.yagmail_to_intermediate_email", "dispname": "-"}, {"name": "emailer_lib.ingress.yagmail_to_intermediate_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/yagmail_to_intermediate_email.html#emailer_lib.yagmail_to_intermediate_email", "dispname": "emailer_lib.yagmail_to_intermediate_email"}, {"name": "emailer_lib.send_intermediate_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_gmail.html#emailer_lib.send_intermediate_email_with_gmail", "dispname": "-"}, {"name": "emailer_lib.egress.send_intermediate_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_gmail.html#emailer_lib.send_intermediate_email_with_gmail", "dispname": "emailer_lib.send_intermediate_email_with_gmail"}, {"name": "emailer_lib.send_intermediate_email_with_smtp", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_smtp.html#emailer_lib.send_intermediate_email_with_smtp", "dispname": "-"}, {"name": "emailer_lib.egress.send_intermediate_email_with_smtp", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_smtp.html#emailer_lib.send_intermediate_email_with_smtp", "dispname": "emailer_lib.send_intermediate_email_with_smtp"}, {"name": "emailer_lib.send_intermediate_email_with_redmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_redmail.html#emailer_lib.send_intermediate_email_with_redmail", "dispname": "-"}, {"name": "emailer_lib.egress.send_intermediate_email_with_redmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_redmail.html#emailer_lib.send_intermediate_email_with_redmail", "dispname": "emailer_lib.send_intermediate_email_with_redmail"}, {"name": "emailer_lib.send_intermediate_email_with_yagmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_yagmail.html#emailer_lib.send_intermediate_email_with_yagmail", "dispname": "-"}, {"name": "emailer_lib.egress.send_intermediate_email_with_yagmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_yagmail.html#emailer_lib.send_intermediate_email_with_yagmail", "dispname": "emailer_lib.send_intermediate_email_with_yagmail"}, {"name": "emailer_lib.send_intermediate_email_with_mailgun", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_mailgun.html#emailer_lib.send_intermediate_email_with_mailgun", "dispname": "-"}, {"name": "emailer_lib.egress.send_intermediate_email_with_mailgun", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_intermediate_email_with_mailgun.html#emailer_lib.send_intermediate_email_with_mailgun", "dispname": "emailer_lib.send_intermediate_email_with_mailgun"}, {"name": "emailer_lib.send_quarto_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_quarto_email_with_gmail.html#emailer_lib.send_quarto_email_with_gmail", "dispname": "-"}, {"name": "emailer_lib.egress.send_quarto_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_quarto_email_with_gmail.html#emailer_lib.send_quarto_email_with_gmail", "dispname": "emailer_lib.send_quarto_email_with_gmail"}, {"name": "emailer_lib.write_email_message_to_file", "domain": "py", "role": "function", "priority": "1", "uri": "reference/write_email_message_to_file.html#emailer_lib.write_email_message_to_file", "dispname": "-"}, {"name": "emailer_lib.utils.write_email_message_to_file", "domain": "py", "role": "function", "priority": "1", "uri": "reference/write_email_message_to_file.html#emailer_lib.write_email_message_to_file", "dispname": "emailer_lib.write_email_message_to_file"}, {"name": "emailer_lib.mjml.mjml", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mjml.html#emailer_lib.mjml.mjml", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.mjml", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mjml.html#emailer_lib.mjml.mjml", "dispname": "emailer_lib.mjml.mjml"}, {"name": "emailer_lib.mjml.head", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.head.html#emailer_lib.mjml.head", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.head", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.head.html#emailer_lib.mjml.head", "dispname": "emailer_lib.mjml.head"}, {"name": "emailer_lib.mjml.body", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.body.html#emailer_lib.mjml.body", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.body", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.body.html#emailer_lib.mjml.body", "dispname": "emailer_lib.mjml.body"}, {"name": "emailer_lib.mjml.mj_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_attributes.html#emailer_lib.mjml.mj_attributes", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.mj_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_attributes.html#emailer_lib.mjml.mj_attributes", "dispname": "emailer_lib.mjml.mj_attributes"}, {"name": "emailer_lib.mjml.mj_all", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_all.html#emailer_lib.mjml.mj_all", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.mj_all", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_all.html#emailer_lib.mjml.mj_all", "dispname": "emailer_lib.mjml.mj_all"}, {"name": "emailer_lib.mjml.mj_class", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_class.html#emailer_lib.mjml.mj_class", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.mj_class", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_class.html#emailer_lib.mjml.mj_class", "dispname": "emailer_lib.mjml.mj_class"}, {"name": "emailer_lib.mjml.breakpoint", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.breakpoint.html#emailer_lib.mjml.breakpoint", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.breakpoint", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.breakpoint.html#emailer_lib.mjml.breakpoint", "dispname": "emailer_lib.mjml.breakpoint"}, {"name": "emailer_lib.mjml.font", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.font.html#emailer_lib.mjml.font", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.font", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.font.html#emailer_lib.mjml.font", "dispname": "emailer_lib.mjml.font"}, {"name": "emailer_lib.mjml.html_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attributes.html#emailer_lib.mjml.html_attributes", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.html_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attributes.html#emailer_lib.mjml.html_attributes", "dispname": "emailer_lib.mjml.html_attributes"}, {"name": "emailer_lib.mjml.html_attribute", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attribute.html#emailer_lib.mjml.html_attribute", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.html_attribute", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attribute.html#emailer_lib.mjml.html_attribute", "dispname": "emailer_lib.mjml.html_attribute"}, {"name": "emailer_lib.mjml.preview", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.preview.html#emailer_lib.mjml.preview", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.preview", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.preview.html#emailer_lib.mjml.preview", "dispname": "emailer_lib.mjml.preview"}, {"name": "emailer_lib.mjml.style", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.style.html#emailer_lib.mjml.style", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.style", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.style.html#emailer_lib.mjml.style", "dispname": "emailer_lib.mjml.style"}, {"name": "emailer_lib.mjml.title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.title.html#emailer_lib.mjml.title", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.title.html#emailer_lib.mjml.title", "dispname": "emailer_lib.mjml.title"}, {"name": "emailer_lib.mjml.accordion", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion.html#emailer_lib.mjml.accordion", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.accordion", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion.html#emailer_lib.mjml.accordion", "dispname": "emailer_lib.mjml.accordion"}, {"name": "emailer_lib.mjml.accordion_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_element.html#emailer_lib.mjml.accordion_element", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.accordion_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_element.html#emailer_lib.mjml.accordion_element", "dispname": "emailer_lib.mjml.accordion_element"}, {"name": "emailer_lib.mjml.accordion_text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_text.html#emailer_lib.mjml.accordion_text", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.accordion_text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_text.html#emailer_lib.mjml.accordion_text", "dispname": "emailer_lib.mjml.accordion_text"}, {"name": "emailer_lib.mjml.accordion_title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_title.html#emailer_lib.mjml.accordion_title", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.accordion_title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_title.html#emailer_lib.mjml.accordion_title", "dispname": "emailer_lib.mjml.accordion_title"}, {"name": "emailer_lib.mjml.button", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.button.html#emailer_lib.mjml.button", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.button", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.button.html#emailer_lib.mjml.button", "dispname": "emailer_lib.mjml.button"}, {"name": "emailer_lib.mjml.carousel", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel.html#emailer_lib.mjml.carousel", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.carousel", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel.html#emailer_lib.mjml.carousel", "dispname": "emailer_lib.mjml.carousel"}, {"name": "emailer_lib.mjml.carousel_image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel_image.html#emailer_lib.mjml.carousel_image", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.carousel_image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel_image.html#emailer_lib.mjml.carousel_image", "dispname": "emailer_lib.mjml.carousel_image"}, {"name": "emailer_lib.mjml.column", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.column.html#emailer_lib.mjml.column", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.column", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.column.html#emailer_lib.mjml.column", "dispname": "emailer_lib.mjml.column"}, {"name": "emailer_lib.mjml.divider", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.divider.html#emailer_lib.mjml.divider", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.divider", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.divider.html#emailer_lib.mjml.divider", "dispname": "emailer_lib.mjml.divider"}, {"name": "emailer_lib.mjml.group", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.group.html#emailer_lib.mjml.group", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.group", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.group.html#emailer_lib.mjml.group", "dispname": "emailer_lib.mjml.group"}, {"name": "emailer_lib.mjml.hero", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.hero.html#emailer_lib.mjml.hero", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.hero", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.hero.html#emailer_lib.mjml.hero", "dispname": "emailer_lib.mjml.hero"}, {"name": "emailer_lib.mjml.image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.image.html#emailer_lib.mjml.image", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.image.html#emailer_lib.mjml.image", "dispname": "emailer_lib.mjml.image"}, {"name": "emailer_lib.mjml.navbar", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar.html#emailer_lib.mjml.navbar", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.navbar", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar.html#emailer_lib.mjml.navbar", "dispname": "emailer_lib.mjml.navbar"}, {"name": "emailer_lib.mjml.navbar_link", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar_link.html#emailer_lib.mjml.navbar_link", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.navbar_link", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar_link.html#emailer_lib.mjml.navbar_link", "dispname": "emailer_lib.mjml.navbar_link"}, {"name": "emailer_lib.mjml.raw", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.raw.html#emailer_lib.mjml.raw", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.raw", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.raw.html#emailer_lib.mjml.raw", "dispname": "emailer_lib.mjml.raw"}, {"name": "emailer_lib.mjml.section", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.section.html#emailer_lib.mjml.section", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.section", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.section.html#emailer_lib.mjml.section", "dispname": "emailer_lib.mjml.section"}, {"name": "emailer_lib.mjml.social", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social.html#emailer_lib.mjml.social", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.social", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social.html#emailer_lib.mjml.social", "dispname": "emailer_lib.mjml.social"}, {"name": "emailer_lib.mjml.social_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social_element.html#emailer_lib.mjml.social_element", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.social_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social_element.html#emailer_lib.mjml.social_element", "dispname": "emailer_lib.mjml.social_element"}, {"name": "emailer_lib.mjml.spacer", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.spacer.html#emailer_lib.mjml.spacer", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.spacer", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.spacer.html#emailer_lib.mjml.spacer", "dispname": "emailer_lib.mjml.spacer"}, {"name": "emailer_lib.mjml.table", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.table.html#emailer_lib.mjml.table", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.table", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.table.html#emailer_lib.mjml.table", "dispname": "emailer_lib.mjml.table"}, {"name": "emailer_lib.mjml.text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.text.html#emailer_lib.mjml.text", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.text.html#emailer_lib.mjml.text", "dispname": "emailer_lib.mjml.text"}, {"name": "emailer_lib.mjml.wrapper", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.wrapper.html#emailer_lib.mjml.wrapper", "dispname": "-"}, {"name": "emailer_lib.mjml.tags.wrapper", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.wrapper.html#emailer_lib.mjml.wrapper", "dispname": "emailer_lib.mjml.wrapper"}]} \ No newline at end of file +{"project": "nbmail", "version": "0.0.9999", "count": 106, "items": [{"name": "nbmail.Email.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/nbmail.Email.preview_send_email.html#nbmail.Email.preview_send_email", "dispname": "-"}, {"name": "nbmail.structs.Email.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/nbmail.Email.preview_send_email.html#nbmail.Email.preview_send_email", "dispname": "nbmail.Email.preview_send_email"}, {"name": "nbmail.Email.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/nbmail.Email.write_email_message.html#nbmail.Email.write_email_message", "dispname": "-"}, {"name": "nbmail.structs.Email.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/nbmail.Email.write_email_message.html#nbmail.Email.write_email_message", "dispname": "nbmail.Email.write_email_message"}, {"name": "nbmail.Email.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/nbmail.Email.write_preview_email.html#nbmail.Email.write_preview_email", "dispname": "-"}, {"name": "nbmail.structs.Email.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/nbmail.Email.write_preview_email.html#nbmail.Email.write_preview_email", "dispname": "nbmail.Email.write_preview_email"}, {"name": "nbmail.Email", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Email.html#nbmail.Email", "dispname": "-"}, {"name": "nbmail.structs.Email", "domain": "py", "role": "class", "priority": "1", "uri": "reference/Email.html#nbmail.Email", "dispname": "nbmail.Email"}, {"name": "nbmail.Email.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Email.write_preview_email.html#nbmail.Email.write_preview_email", "dispname": "-"}, {"name": "nbmail.structs.Email.write_preview_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Email.write_preview_email.html#nbmail.Email.write_preview_email", "dispname": "nbmail.Email.write_preview_email"}, {"name": "nbmail.Email.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Email.write_email_message.html#nbmail.Email.write_email_message", "dispname": "-"}, {"name": "nbmail.structs.Email.write_email_message", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Email.write_email_message.html#nbmail.Email.write_email_message", "dispname": "nbmail.Email.write_email_message"}, {"name": "nbmail.Email.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Email.preview_send_email.html#nbmail.Email.preview_send_email", "dispname": "-"}, {"name": "nbmail.structs.Email.preview_send_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/Email.preview_send_email.html#nbmail.Email.preview_send_email", "dispname": "nbmail.Email.preview_send_email"}, {"name": "nbmail.quarto_json_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/quarto_json_to_email.html#nbmail.quarto_json_to_email", "dispname": "-"}, {"name": "nbmail.ingress.quarto_json_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/quarto_json_to_email.html#nbmail.quarto_json_to_email", "dispname": "nbmail.quarto_json_to_email"}, {"name": "nbmail.mjml_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml_to_email.html#nbmail.mjml_to_email", "dispname": "-"}, {"name": "nbmail.ingress.mjml_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml_to_email.html#nbmail.mjml_to_email", "dispname": "nbmail.mjml_to_email"}, {"name": "nbmail.redmail_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/redmail_to_email.html#nbmail.redmail_to_email", "dispname": "-"}, {"name": "nbmail.ingress.redmail_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/redmail_to_email.html#nbmail.redmail_to_email", "dispname": "nbmail.redmail_to_email"}, {"name": "nbmail.yagmail_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/yagmail_to_email.html#nbmail.yagmail_to_email", "dispname": "-"}, {"name": "nbmail.ingress.yagmail_to_email", "domain": "py", "role": "function", "priority": "1", "uri": "reference/yagmail_to_email.html#nbmail.yagmail_to_email", "dispname": "nbmail.yagmail_to_email"}, {"name": "nbmail.send_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_gmail.html#nbmail.send_email_with_gmail", "dispname": "-"}, {"name": "nbmail.egress.send_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_gmail.html#nbmail.send_email_with_gmail", "dispname": "nbmail.send_email_with_gmail"}, {"name": "nbmail.send_email_with_smtp", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_smtp.html#nbmail.send_email_with_smtp", "dispname": "-"}, {"name": "nbmail.egress.send_email_with_smtp", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_smtp.html#nbmail.send_email_with_smtp", "dispname": "nbmail.send_email_with_smtp"}, {"name": "nbmail.send_email_with_redmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_redmail.html#nbmail.send_email_with_redmail", "dispname": "-"}, {"name": "nbmail.egress.send_email_with_redmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_redmail.html#nbmail.send_email_with_redmail", "dispname": "nbmail.send_email_with_redmail"}, {"name": "nbmail.send_email_with_yagmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_yagmail.html#nbmail.send_email_with_yagmail", "dispname": "-"}, {"name": "nbmail.egress.send_email_with_yagmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_yagmail.html#nbmail.send_email_with_yagmail", "dispname": "nbmail.send_email_with_yagmail"}, {"name": "nbmail.send_email_with_mailgun", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_mailgun.html#nbmail.send_email_with_mailgun", "dispname": "-"}, {"name": "nbmail.egress.send_email_with_mailgun", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_email_with_mailgun.html#nbmail.send_email_with_mailgun", "dispname": "nbmail.send_email_with_mailgun"}, {"name": "nbmail.send_quarto_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_quarto_email_with_gmail.html#nbmail.send_quarto_email_with_gmail", "dispname": "-"}, {"name": "nbmail.egress.send_quarto_email_with_gmail", "domain": "py", "role": "function", "priority": "1", "uri": "reference/send_quarto_email_with_gmail.html#nbmail.send_quarto_email_with_gmail", "dispname": "nbmail.send_quarto_email_with_gmail"}, {"name": "nbmail.write_email_message_to_file", "domain": "py", "role": "function", "priority": "1", "uri": "reference/write_email_message_to_file.html#nbmail.write_email_message_to_file", "dispname": "-"}, {"name": "nbmail.utils.write_email_message_to_file", "domain": "py", "role": "function", "priority": "1", "uri": "reference/write_email_message_to_file.html#nbmail.write_email_message_to_file", "dispname": "nbmail.write_email_message_to_file"}, {"name": "nbmail.mjml.mjml", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mjml.html#nbmail.mjml.mjml", "dispname": "-"}, {"name": "nbmail.mjml.tags.mjml", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mjml.html#nbmail.mjml.mjml", "dispname": "nbmail.mjml.mjml"}, {"name": "nbmail.mjml.head", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.head.html#nbmail.mjml.head", "dispname": "-"}, {"name": "nbmail.mjml.tags.head", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.head.html#nbmail.mjml.head", "dispname": "nbmail.mjml.head"}, {"name": "nbmail.mjml.body", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.body.html#nbmail.mjml.body", "dispname": "-"}, {"name": "nbmail.mjml.tags.body", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.body.html#nbmail.mjml.body", "dispname": "nbmail.mjml.body"}, {"name": "nbmail.mjml.mj_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_attributes.html#nbmail.mjml.mj_attributes", "dispname": "-"}, {"name": "nbmail.mjml.tags.mj_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_attributes.html#nbmail.mjml.mj_attributes", "dispname": "nbmail.mjml.mj_attributes"}, {"name": "nbmail.mjml.mj_all", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_all.html#nbmail.mjml.mj_all", "dispname": "-"}, {"name": "nbmail.mjml.tags.mj_all", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_all.html#nbmail.mjml.mj_all", "dispname": "nbmail.mjml.mj_all"}, {"name": "nbmail.mjml.mj_class", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_class.html#nbmail.mjml.mj_class", "dispname": "-"}, {"name": "nbmail.mjml.tags.mj_class", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.mj_class.html#nbmail.mjml.mj_class", "dispname": "nbmail.mjml.mj_class"}, {"name": "nbmail.mjml.breakpoint", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.breakpoint.html#nbmail.mjml.breakpoint", "dispname": "-"}, {"name": "nbmail.mjml.tags.breakpoint", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.breakpoint.html#nbmail.mjml.breakpoint", "dispname": "nbmail.mjml.breakpoint"}, {"name": "nbmail.mjml.font", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.font.html#nbmail.mjml.font", "dispname": "-"}, {"name": "nbmail.mjml.tags.font", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.font.html#nbmail.mjml.font", "dispname": "nbmail.mjml.font"}, {"name": "nbmail.mjml.html_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attributes.html#nbmail.mjml.html_attributes", "dispname": "-"}, {"name": "nbmail.mjml.tags.html_attributes", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attributes.html#nbmail.mjml.html_attributes", "dispname": "nbmail.mjml.html_attributes"}, {"name": "nbmail.mjml.html_attribute", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attribute.html#nbmail.mjml.html_attribute", "dispname": "-"}, {"name": "nbmail.mjml.tags.html_attribute", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.html_attribute.html#nbmail.mjml.html_attribute", "dispname": "nbmail.mjml.html_attribute"}, {"name": "nbmail.mjml.preview", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.preview.html#nbmail.mjml.preview", "dispname": "-"}, {"name": "nbmail.mjml.tags.preview", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.preview.html#nbmail.mjml.preview", "dispname": "nbmail.mjml.preview"}, {"name": "nbmail.mjml.style", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.style.html#nbmail.mjml.style", "dispname": "-"}, {"name": "nbmail.mjml.tags.style", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.style.html#nbmail.mjml.style", "dispname": "nbmail.mjml.style"}, {"name": "nbmail.mjml.title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.title.html#nbmail.mjml.title", "dispname": "-"}, {"name": "nbmail.mjml.tags.title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.title.html#nbmail.mjml.title", "dispname": "nbmail.mjml.title"}, {"name": "nbmail.mjml.accordion", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion.html#nbmail.mjml.accordion", "dispname": "-"}, {"name": "nbmail.mjml.tags.accordion", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion.html#nbmail.mjml.accordion", "dispname": "nbmail.mjml.accordion"}, {"name": "nbmail.mjml.accordion_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_element.html#nbmail.mjml.accordion_element", "dispname": "-"}, {"name": "nbmail.mjml.tags.accordion_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_element.html#nbmail.mjml.accordion_element", "dispname": "nbmail.mjml.accordion_element"}, {"name": "nbmail.mjml.accordion_text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_text.html#nbmail.mjml.accordion_text", "dispname": "-"}, {"name": "nbmail.mjml.tags.accordion_text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_text.html#nbmail.mjml.accordion_text", "dispname": "nbmail.mjml.accordion_text"}, {"name": "nbmail.mjml.accordion_title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_title.html#nbmail.mjml.accordion_title", "dispname": "-"}, {"name": "nbmail.mjml.tags.accordion_title", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.accordion_title.html#nbmail.mjml.accordion_title", "dispname": "nbmail.mjml.accordion_title"}, {"name": "nbmail.mjml.button", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.button.html#nbmail.mjml.button", "dispname": "-"}, {"name": "nbmail.mjml.tags.button", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.button.html#nbmail.mjml.button", "dispname": "nbmail.mjml.button"}, {"name": "nbmail.mjml.carousel", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel.html#nbmail.mjml.carousel", "dispname": "-"}, {"name": "nbmail.mjml.tags.carousel", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel.html#nbmail.mjml.carousel", "dispname": "nbmail.mjml.carousel"}, {"name": "nbmail.mjml.carousel_image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel_image.html#nbmail.mjml.carousel_image", "dispname": "-"}, {"name": "nbmail.mjml.tags.carousel_image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.carousel_image.html#nbmail.mjml.carousel_image", "dispname": "nbmail.mjml.carousel_image"}, {"name": "nbmail.mjml.column", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.column.html#nbmail.mjml.column", "dispname": "-"}, {"name": "nbmail.mjml.tags.column", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.column.html#nbmail.mjml.column", "dispname": "nbmail.mjml.column"}, {"name": "nbmail.mjml.divider", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.divider.html#nbmail.mjml.divider", "dispname": "-"}, {"name": "nbmail.mjml.tags.divider", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.divider.html#nbmail.mjml.divider", "dispname": "nbmail.mjml.divider"}, {"name": "nbmail.mjml.group", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.group.html#nbmail.mjml.group", "dispname": "-"}, {"name": "nbmail.mjml.tags.group", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.group.html#nbmail.mjml.group", "dispname": "nbmail.mjml.group"}, {"name": "nbmail.mjml.hero", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.hero.html#nbmail.mjml.hero", "dispname": "-"}, {"name": "nbmail.mjml.tags.hero", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.hero.html#nbmail.mjml.hero", "dispname": "nbmail.mjml.hero"}, {"name": "nbmail.mjml.image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.image.html#nbmail.mjml.image", "dispname": "-"}, {"name": "nbmail.mjml.tags.image", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.image.html#nbmail.mjml.image", "dispname": "nbmail.mjml.image"}, {"name": "nbmail.mjml.navbar", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar.html#nbmail.mjml.navbar", "dispname": "-"}, {"name": "nbmail.mjml.tags.navbar", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar.html#nbmail.mjml.navbar", "dispname": "nbmail.mjml.navbar"}, {"name": "nbmail.mjml.navbar_link", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar_link.html#nbmail.mjml.navbar_link", "dispname": "-"}, {"name": "nbmail.mjml.tags.navbar_link", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.navbar_link.html#nbmail.mjml.navbar_link", "dispname": "nbmail.mjml.navbar_link"}, {"name": "nbmail.mjml.raw", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.raw.html#nbmail.mjml.raw", "dispname": "-"}, {"name": "nbmail.mjml.tags.raw", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.raw.html#nbmail.mjml.raw", "dispname": "nbmail.mjml.raw"}, {"name": "nbmail.mjml.section", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.section.html#nbmail.mjml.section", "dispname": "-"}, {"name": "nbmail.mjml.tags.section", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.section.html#nbmail.mjml.section", "dispname": "nbmail.mjml.section"}, {"name": "nbmail.mjml.social", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social.html#nbmail.mjml.social", "dispname": "-"}, {"name": "nbmail.mjml.tags.social", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social.html#nbmail.mjml.social", "dispname": "nbmail.mjml.social"}, {"name": "nbmail.mjml.social_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social_element.html#nbmail.mjml.social_element", "dispname": "-"}, {"name": "nbmail.mjml.tags.social_element", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.social_element.html#nbmail.mjml.social_element", "dispname": "nbmail.mjml.social_element"}, {"name": "nbmail.mjml.spacer", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.spacer.html#nbmail.mjml.spacer", "dispname": "-"}, {"name": "nbmail.mjml.tags.spacer", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.spacer.html#nbmail.mjml.spacer", "dispname": "nbmail.mjml.spacer"}, {"name": "nbmail.mjml.table", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.table.html#nbmail.mjml.table", "dispname": "-"}, {"name": "nbmail.mjml.tags.table", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.table.html#nbmail.mjml.table", "dispname": "nbmail.mjml.table"}, {"name": "nbmail.mjml.text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.text.html#nbmail.mjml.text", "dispname": "-"}, {"name": "nbmail.mjml.tags.text", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.text.html#nbmail.mjml.text", "dispname": "nbmail.mjml.text"}, {"name": "nbmail.mjml.wrapper", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.wrapper.html#nbmail.mjml.wrapper", "dispname": "-"}, {"name": "nbmail.mjml.tags.wrapper", "domain": "py", "role": "function", "priority": "1", "uri": "reference/mjml.wrapper.html#nbmail.mjml.wrapper", "dispname": "nbmail.mjml.wrapper"}]} diff --git a/docs/summary.quarto_ipynb b/docs/summary.quarto_ipynb new file mode 100644 index 0000000..d12aa5d --- /dev/null +++ b/docs/summary.quarto_ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The whole game\n", + "\n", + "\n", + "\n", + "Emailing reports is a critical but challenging task for data science.\n", + "Mainly because you have to figure out generating the email content, configuring pieces like attachments, and orchestrating it (e.g. testing, or sending on a schedule). Moreover, content can range from simple layouts to more complex ones.\n", + "\n", + "In this tutorial, we'll walk through the whole game of sending email. We'll start with this simple example:\n" + ], + "id": "a59ad40b" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "# | eval: false\n", + "import os\n", + "from dotenv import load_dotenv\n", + "from data_polars import sp500\n", + "import redmail\n", + "\n", + "load_dotenv()\n", + "gmail_address = os.environ[\"GMAIL_ADDRESS\"]\n", + "gmail_app_password = os.environ[\"GMAIL_APP_PASSWORD\"]\n", + "\n", + "\n", + "email_subject = \"Report on Cars\"\n", + "email_body = sp500.head(10).style.as_raw_html(inline_css=True)\n", + "\n", + "# This is here to emphasize the sender does not have to be the same as the receiver\n", + "email_receiver = gmail_address\n", + "\n", + "redmail.gmail.username = gmail_address\n", + "redmail.gmail.password = gmail_app_password\n", + "\n", + "redmail.gmail.send(\n", + " subject=email_subject,\n", + " receivers=[email_receiver],\n", + " html=email_body,\n", + ")" + ], + "id": "1e722fc3", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "{width=50% fig-align=\"center\"}\n", + "\n", + "* **Content**: writing the text of the email, including plots and tables.\n", + "* **Composing**: setting up the subject, sender, and receivers.\n", + "* **Orchestrating**: previewing, testing, and scheduling the email.\n", + "\n", + "We'll also quickly review writing more advanced content layouts, and authoring email reports that involve running code with Quarto.\n", + "\n", + "## A simple email\n", + "\n", + "{width=50% fig-align=\"center\"}\n", + "\n", + "* Generate and preview\n", + "* Authenticate (may need to refer to its own authentication page in guide)\n", + "* Send\n" + ], + "id": "466f45ac" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | code-fold: true\n", + "# | eval: false\n", + "import os\n", + "from dotenv import load_dotenv\n", + "from data_polars import sp500\n", + "import redmail\n", + "\n", + "load_dotenv()\n", + "gmail_address = os.environ[\"GMAIL_ADDRESS\"]\n", + "gmail_app_password = os.environ[\"GMAIL_APP_PASSWORD\"]\n", + "\n", + "\n", + "email_subject = \"Report on Cars\"\n", + "email_body = sp500.head(10).style.as_raw_html(inline_css=True)\n", + "\n", + "# This is here to emphasize the sender does not have to be the same as the receiver\n", + "email_receiver = gmail_address\n", + "\n", + "redmail.gmail.username = gmail_address\n", + "redmail.gmail.password = gmail_app_password\n", + "\n", + "redmail.gmail.send(\n", + " subject=email_subject,\n", + " receivers=[email_receiver],\n", + " html=email_body,\n", + ")" + ], + "id": "d7f3dc30", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure: subject, recipients, attachments\n", + "\n", + "* you could attach the data as a CSV attachment\n", + "\n", + "\n", + "## Orchestrate: save and preview\n", + "\n", + "* previewing email\n", + "* intermediate json, easy for sending email later\n", + "* embedding images makes previewing hard\n", + "* can always email to yourself (or use a test service like Litmus)\n", + "\n", + "## Content: Quarto authoring\n", + "\n", + "Here's our same simple email generated using quarto.\n", + "\n", + "\n", + "\n", + "{width=50% fig-align=\"center\"}\n", + "\n", + "* Focused on basic configuring, and content\n", + "* Sending happens via our tool\n", + "* Generate using quarto render\n", + "* Can preview email\n", + "\n", + "## Content: advanced layouts\n", + "\n", + "We'll highlight the key pieces (discussed later in this guide) to go from that simple email, to a more advanced on like below:\n", + "\n", + "{width=50% fig-align=\"center\"}\n", + "\n", + "\n", + "## Fridge\n", + "\n", + "In this tutorial, we are going to send an email from a Gmail account. To do so, you will need to [create an App Password](https://support.google.com/accounts/answer/185833). Note this is only possible if you've [enabled 2-step verification](https://support.google.com/accounts/answer/185839).\n", + "\n", + "\n", + "\n", + "::: {.callout-tip}\n", + "This is just one of many options: it is also possible to send emails in Python from other email providers (Outlook, ProtonMail, etc.), or even from a custom domain. To skip ahead to a discussion of alternative sending methods, see [Authentication](orchestrating-auth.qmd)\n", + ":::\n", + "\n", + "Once you've created your App Password, that is used as your Gmail password for sending with Python. \n", + "\n", + "There are many ways to store the password seperate from your email-sending code, so as to not expose any sensitive information. One such approach uses a `.env` file, and the ``dotenv` and `os` packages.\n", + "\n", + "```{.sh filename=\".env\"}\n", + "GMAIL_APP_PASSWORD=abcd abcd abcd abcd\n", + "```\n", + "\n", + "```{.python filename=\"main.py\"}\n", + "import os\n", + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv()\n", + "\n", + "your_gmail_address = \"YourGmail@gmail.com\"\n", + "gmail_app_password = os.environ[\"GMAIL_APP_PASSWORD\"]\n", + "```\n", + "\n", + "Check out the email content we will send.\n" + ], + "id": "a0472e78" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from data_polars import sp500\n", + "\n", + "sp500.head(10).style" + ], + "id": "b0858409", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we send the email!\n" + ], + "id": "e696c7a5" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# | eval: false\n", + "import redmail\n", + "\n", + "redmail.gmail.username = your_gmail_address\n", + "redmail.gmail.password = gmail_app_password\n", + "\n", + "redmail.gmail.send(\n", + " subject=\"An Example Email\",\n", + " receivers=[username],\n", + " html=email_html,\n", + " text=email_plaintext,\n", + ")" + ], + "id": "9fbe68e8", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ], + "id": "39984027" + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)", + "path": "/Users/jules-wg/Library/Python/3.9/share/jupyter/kernels/python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/emailer_lib/__init__.py b/emailer_lib/__init__.py deleted file mode 100644 index dc25a87..0000000 --- a/emailer_lib/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from .ingress import ( - redmail_to_intermediate_email, - yagmail_to_intermediate_email, - mjml_to_intermediate_email, - quarto_json_to_intermediate_email, -) - -from .egress import ( - send_quarto_email_with_gmail, - send_intermediate_email_with_gmail, - send_intermediate_email_with_redmail, - send_intermediate_email_with_yagmail, - send_intermediate_email_with_mailgun, - send_intermediate_email_with_smtp, -) - -from .utils import write_email_message_to_file - -from .structs import IntermediateEmail - - -__all__ = [ - "quarto_json_to_intermediate_email", - "IntermediateEmail", - "redmail_to_intermediate_email", - "yagmail_to_intermediate_email", - "mjml_to_intermediate_email", - "send_quarto_email_with_gmail", - "send_intermediate_email_with_gmail", - "send_intermediate_email_with_redmail", - "send_intermediate_email_with_yagmail", - "send_intermediate_email_with_mailgun", - "send_intermediate_email_with_smtp", - "write_email_message_to_file", -] diff --git a/examples/.gitignore b/examples/.gitignore index bd2bfd4..a131850 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,2 +1,2 @@ playground.qmd -emailer_lib.qmd \ No newline at end of file +nbmail.qmd diff --git a/examples/emailer_lib.qmd b/examples/emailer_lib.qmd new file mode 100644 index 0000000..e69de29 diff --git a/nbmail/__init__.py b/nbmail/__init__.py new file mode 100644 index 0000000..8c933b1 --- /dev/null +++ b/nbmail/__init__.py @@ -0,0 +1,35 @@ +from .ingress import ( + redmail_to_email, + yagmail_to_email, + mjml_to_email, + quarto_json_to_email, +) + +from .egress import ( + send_quarto_email_with_gmail, + send_email_with_gmail, + send_email_with_redmail, + send_email_with_yagmail, + send_email_with_mailgun, + send_email_with_smtp, +) + +from .utils import write_email_message_to_file + +from .structs import Email + + +__all__ = [ + "quarto_json_to_email", + "Email", + "redmail_to_email", + "yagmail_to_email", + "mjml_to_email", + "send_quarto_email_with_gmail", + "send_email_with_gmail", + "send_email_with_redmail", + "send_email_with_yagmail", + "send_email_with_mailgun", + "send_email_with_smtp", + "write_email_message_to_file", +] diff --git a/emailer_lib/egress.py b/nbmail/egress.py similarity index 83% rename from emailer_lib/egress.py rename to nbmail/egress.py index 8ce18aa..ba8ec70 100644 --- a/emailer_lib/egress.py +++ b/nbmail/egress.py @@ -12,18 +12,18 @@ from email import encoders from typing import Literal -from .ingress import quarto_json_to_intermediate_email +from .ingress import quarto_json_to_email -from .structs import IntermediateEmail +from .structs import Email import warnings __all__ = [ "send_quarto_email_with_gmail", - "send_intermediate_email_with_gmail", - "send_intermediate_email_with_redmail", - "send_intermediate_email_with_yagmail", - "send_intermediate_email_with_mailgun", - "send_intermediate_email_with_smtp", + "send_email_with_gmail", + "send_email_with_redmail", + "send_email_with_yagmail", + "send_email_with_mailgun", + "send_email_with_smtp", ] @@ -68,9 +68,9 @@ def send_quarto_email_with_gmail( ) ``` """ - i_email: IntermediateEmail = quarto_json_to_intermediate_email(json_path) + i_email: Email = quarto_json_to_email(json_path) i_email.recipients = recipients - send_intermediate_email_with_gmail( + send_email_with_gmail( username=username, password=password, i_email=i_email ) @@ -79,11 +79,11 @@ def send_quarto_email_with_gmail( # Could also take creds object -def send_intermediate_email_with_gmail( - username: str, password: str, i_email: IntermediateEmail +def send_email_with_gmail( + username: str, password: str, i_email: Email ): """ - Send an Intermediate Email object via Gmail. + Send an Email object via Gmail. Parameters ---------- @@ -94,7 +94,7 @@ def send_intermediate_email_with_gmail( Gmail app password i_email - IntermediateEmail object containing the email content and attachments + Email object containing the email content and attachments Returns ------- @@ -104,17 +104,17 @@ def send_intermediate_email_with_gmail( Examples -------- ```python - email = IntermediateEmail( + email = Email( html="
Hello world
", subject="Test Email", recipients=["user@example.com"], ) - send_intermediate_email_with_gmail("user@gmail.com", "password123", email) + send_email_with_gmail("user@gmail.com", "password123", email) ``` """ # Compose the email - return send_intermediate_email_with_smtp( + return send_email_with_smtp( smtp_host="smtp.gmail.com", smtp_port=587, username=username, @@ -124,14 +124,14 @@ def send_intermediate_email_with_gmail( ) -def send_intermediate_email_with_redmail(i_email: IntermediateEmail): +def send_email_with_redmail(i_email: Email): """ - Send an Intermediate Email object via Redmail. + Send an Email object via Redmail. Parameters ---------- i_email - IntermediateEmail object containing the email content and attachments + Email object containing the email content and attachments Returns ------- @@ -144,14 +144,14 @@ def send_intermediate_email_with_redmail(i_email: IntermediateEmail): raise NotImplementedError -def send_intermediate_email_with_yagmail(i_email: IntermediateEmail): +def send_email_with_yagmail(i_email: Email): """ - Send an Intermediate Email object via Yagmail. + Send an Email object via Yagmail. Parameters ---------- i_email - IntermediateEmail object containing the email content and attachments + Email object containing the email content and attachments Returns ------- @@ -165,14 +165,14 @@ def send_intermediate_email_with_yagmail(i_email: IntermediateEmail): raise NotImplementedError -def send_intermediate_email_with_mailgun( +def send_email_with_mailgun( api_key: str, domain: str, sender: str, - i_email: IntermediateEmail, + i_email: Email, ): """ - Send an Intermediate Email object via Mailgun. + Send an Email object via Mailgun. Parameters ---------- @@ -183,7 +183,7 @@ def send_intermediate_email_with_mailgun( sender Email address to send from (must be authorized in your domain) i_email - IntermediateEmail object containing the email content and attachments + Email object containing the email content and attachments Returns ------- @@ -198,13 +198,13 @@ def send_intermediate_email_with_mailgun( Examples -------- ```python - email = IntermediateEmail( + email = Email( html="Hello world
", subject="Test Email", recipients=["user@example.com"], ) - response = send_intermediate_email_with_mailgun( + response = send_email_with_mailgun( api_key="your-api-key", domain="mg.yourdomain.com", sender="noreply@yourdomain.com", @@ -265,16 +265,16 @@ def send_intermediate_email_with_mailgun( return response -def send_intermediate_email_with_smtp( +def send_email_with_smtp( smtp_host: str, smtp_port: int, username: str, password: str, - i_email: IntermediateEmail, + i_email: Email, security: str = Literal["tls", "ssl", "smtp"], ): """ - Send an Intermediate Email object via SMTP. + Send an Email object via SMTP. Parameters ---------- @@ -291,7 +291,7 @@ def send_intermediate_email_with_smtp( SMTP account password i_email - IntermediateEmail object containing the email content and attachments + Email object containing the email content and attachments security Security protocol to use: "tls" (STARTTLS), "ssl" (SSL/TLS), or "smtp" (plain SMTP). @@ -310,14 +310,14 @@ def send_intermediate_email_with_smtp( Examples -------- ```python - email = IntermediateEmail( + email = Email( html="Hello world
", subject="Test Email", recipients=["user@example.com"], ) # TLS connection (port 587) - recommended - send_intermediate_email_with_smtp( + send_email_with_smtp( "smtp.example.com", 587, "user@example.com", @@ -327,7 +327,7 @@ def send_intermediate_email_with_smtp( ) # SSL connection (port 465) - send_intermediate_email_with_smtp( + send_email_with_smtp( "smtp.example.com", 465, "user@example.com", @@ -337,7 +337,7 @@ def send_intermediate_email_with_smtp( ) # Plain SMTP (port 25) - insecure, for testing only - send_intermediate_email_with_smtp( + send_email_with_smtp( "127.0.0.1", 8025, "test@example.com", diff --git a/emailer_lib/ingress.py b/nbmail/ingress.py similarity index 81% rename from emailer_lib/ingress.py rename to nbmail/ingress.py index e92e09a..005686e 100644 --- a/emailer_lib/ingress.py +++ b/nbmail/ingress.py @@ -5,36 +5,36 @@ from email.message import EmailMessage from mjml import mjml2html -from .structs import IntermediateEmail +from .structs import Email from .mjml import MJMLTag from .mjml.image_processor import _process_mjml_images import warnings __all__ = [ - "redmail_to_intermediate_email", - "yagmail_to_intermediate_email", - "mjml_to_intermediate_email", - "quarto_json_to_intermediate_email", + "redmail_to_email", + "yagmail_to_email", + "mjml_to_email", + "quarto_json_to_email", ] -def redmail_to_intermediate_email(msg: EmailMessage) -> IntermediateEmail: +def redmail_to_email(msg: EmailMessage) -> Email: """ - Convert a Redmail EmailMessage object to an IntermediateEmail + Convert a Redmail EmailMessage object to an Email Params ------ msg The Redmail-generated EmailMessage object - Converts the input EmailMessage to the intermediate email structure + Converts the input EmailMessage to the email structure """ - return _email_message_to_intermediate_email(msg) + return _email_message_to_email(msg) -def yagmail_to_intermediate_email(): +def yagmail_to_email(): """ - Convert a Yagmail email object to an IntermediateEmail + Convert a Yagmail email object to an Email Params ------ @@ -45,11 +45,11 @@ def yagmail_to_intermediate_email(): pass -def mjml_to_intermediate_email( +def mjml_to_email( mjml_content: str | MJMLTag, -) -> IntermediateEmail: +) -> Email: """ - Convert MJML markup to an IntermediateEmail + Convert MJML markup to an Email Parameters ---------- @@ -58,12 +58,14 @@ def mjml_to_intermediate_email( Returns ------ - An Intermediate Email object + An Email object """ # Handle MJMLTag objects by preprocessing images if isinstance(mjml_content, MJMLTag): - processed_mjml, inline_attachments = _process_mjml_images(mjml_content) + # Wrap in proper MJML structure if needed + wrapped_mjml = mjml_content._wrap_in_mjml_tag(emit_warning=True) + processed_mjml, inline_attachments = _process_mjml_images(wrapped_mjml) mjml_markup = processed_mjml._to_mjml() else: # String-based MJML, no preprocessing needed @@ -73,7 +75,7 @@ def mjml_to_intermediate_email( email_content = mjml2html(mjml_markup) - i_email = IntermediateEmail( + i_email = Email( html=email_content, subject="", rsc_email_supress_report_attachment=False, @@ -85,9 +87,9 @@ def mjml_to_intermediate_email( # useful because redmail bundles an email message... may help in other cases too -def _email_message_to_intermediate_email(msg: EmailMessage) -> IntermediateEmail: +def _email_message_to_email(msg: EmailMessage) -> Email: """ - Convert a Python EmailMessage object to an IntermediateEmail + Convert a Python EmailMessage object to an Email Parameters ------ @@ -147,7 +149,7 @@ def _email_message_to_intermediate_email(msg: EmailMessage) -> IntermediateEmail # Not certain that all attached files have associated filenames external_attachments.append(filename) - return IntermediateEmail( + return Email( html=html or "", subject=subject, external_attachments=external_attachments if external_attachments else None, @@ -158,9 +160,9 @@ def _email_message_to_intermediate_email(msg: EmailMessage) -> IntermediateEmail # Helper method to parse the quarto JSON -def quarto_json_to_intermediate_email(path: str) -> IntermediateEmail: +def quarto_json_to_email(path: str) -> Email: """ - Convert a Quarto output metadata JSON file to an IntermediateEmail + Convert a Quarto output metadata JSON file to an Email Parameters ------ @@ -190,7 +192,7 @@ def quarto_json_to_intermediate_email(path: str) -> IntermediateEmail: ) supress_scheduled = metadata.get("rsc_email_supress_scheduled", False) - i_email = IntermediateEmail( + i_email = Email( html=email_html, text=email_text, inline_attachments=email_images, diff --git a/emailer_lib/mjml/.gitignore b/nbmail/mjml/.gitignore similarity index 100% rename from emailer_lib/mjml/.gitignore rename to nbmail/mjml/.gitignore diff --git a/emailer_lib/mjml/README.md b/nbmail/mjml/README.md similarity index 91% rename from emailer_lib/mjml/README.md rename to nbmail/mjml/README.md index 16efad3..2b3c624 100644 --- a/emailer_lib/mjml/README.md +++ b/nbmail/mjml/README.md @@ -14,16 +14,16 @@ This module provides Python functions for creating MJML markup, the responsive e ## Installation -This module is part of the `emailer_lib` package: +This module is part of the `nbmail` package: ```python -from emailer_lib import mjml as mj +from nbmail import mjml as mj ``` ## Quick Start ```python -from emailer_lib.mjml import mjml, body, section, column, text +from nbmail.mjml import mjml, body, section, column, text # Build an MJML email structure email = mjml( @@ -99,7 +99,7 @@ button( The base class for all MJML elements. Can be instantiated directly or via helper functions. ```python -from emailer_lib.mjml import MJMLTag +from nbmail.mjml import MJMLTag tag = MJMLTag( "mj-text", @@ -117,7 +117,7 @@ A dictionary type for tag attributes. ### Simple Email ```python -from emailer_lib.mjml import mjml, head, body, section, column, text, title +from nbmail.mjml import mjml, head, body, section, column, text, title email = mjml( head( @@ -136,7 +136,7 @@ email = mjml( ### Multi-column Layout ```python -from emailer_lib.mjml import body, section, column, text, image +from nbmail.mjml import body, section, column, text, image layout = body( section( @@ -157,7 +157,7 @@ layout = body( ### Using Attributes ```python -from emailer_lib.mjml import section, column, text +from nbmail.mjml import section, column, text # Attributes as kwargs section( diff --git a/emailer_lib/mjml/__init__.py b/nbmail/mjml/__init__.py similarity index 94% rename from emailer_lib/mjml/__init__.py rename to nbmail/mjml/__init__.py index ea3b2f1..3720161 100644 --- a/emailer_lib/mjml/__init__.py +++ b/nbmail/mjml/__init__.py @@ -39,7 +39,7 @@ navbar_link, social_element, ) -# _process_mjml_images is called internally by mjml_to_intermediate_email +# _process_mjml_images is called internally by mjml_to_email __all__ = ( "MJMLTag", diff --git a/emailer_lib/mjml/_core.py b/nbmail/mjml/_core.py similarity index 81% rename from emailer_lib/mjml/_core.py rename to nbmail/mjml/_core.py index d55913e..9331639 100644 --- a/emailer_lib/mjml/_core.py +++ b/nbmail/mjml/_core.py @@ -132,7 +132,7 @@ def _to_mjml(self, indent: int = 0, eol: str = "\n") -> str: Ported from htmltools Tag rendering logic. Note: BytesIO/bytes in image src attributes are not supported by _to_mjml(). - Pass the MJMLTag directly to mjml_to_intermediate_email() instead. + Pass the MJMLTag directly to mjml_to_email() instead. """ def _flatten(children): @@ -150,8 +150,8 @@ def _flatten(children): if isinstance(src_value, (bytes, BytesIO)): raise ValueError( "Cannot render MJML with BytesIO/bytes in image src attribute. " - "Pass the MJMLTag object directly to mjml_to_intermediate_email() instead of calling _to_mjml() first. " - "Example: i_email = mjml_to_intermediate_email(doc)" + "Pass the MJMLTag object directly to mjml_to_email() instead of calling _to_mjml() first. " + "Example: i_email = mjml_to_email(doc)" ) # Build attribute string @@ -181,8 +181,8 @@ def _flatten(children): return f"{pad}<{self.tagName}{attr_str}>{self.tagName}>" def _repr_html_(self): - from ..ingress import mjml_to_intermediate_email - return mjml_to_intermediate_email(self)._repr_html_() + from ..ingress import mjml_to_email + return mjml_to_email(self)._repr_html_() # TODO: make something deliberate def __repr__(self) -> str: @@ -193,7 +193,47 @@ def __repr__(self) -> str: ) return f"Hello 
Hi
", subject="Test", recipients=["a@example.com"], @@ -31,22 +31,22 @@ def setup_smtp_mocks(monkeypatch): context = mock_smtp.return_value.__enter__.return_value - # Patch in the emailer_lib.egress module where they're used - monkeypatch.setattr("emailer_lib.egress.smtplib.SMTP", mock_smtp) - monkeypatch.setattr("emailer_lib.egress.smtplib.SMTP_SSL", mock_smtp_ssl) + # Patch in the nbmail.egress module where they're used + monkeypatch.setattr("nbmail.egress.smtplib.SMTP", mock_smtp) + monkeypatch.setattr("nbmail.egress.smtplib.SMTP_SSL", mock_smtp_ssl) return mock_smtp, mock_smtp_ssl, context -def test_send_intermediate_email_with_gmail_calls_smtp(monkeypatch): +def test_send_email_with_gmail_calls_smtp(monkeypatch): email = make_basic_email() mock_smtp_send = MagicMock() monkeypatch.setattr( - "emailer_lib.egress.send_intermediate_email_with_smtp", mock_smtp_send + "nbmail.egress.send_email_with_smtp", mock_smtp_send ) - send_intermediate_email_with_gmail("user@gmail.com", "pass", email) + send_email_with_gmail("user@gmail.com", "pass", email) mock_smtp_send.assert_called_once_with( smtp_host="smtp.gmail.com", @@ -58,11 +58,11 @@ def test_send_intermediate_email_with_gmail_calls_smtp(monkeypatch): ) -def test_send_intermediate_email_with_smtp_tls(monkeypatch): +def test_send_email_with_smtp_tls(monkeypatch): email = make_basic_email() mock_smtp, mock_smtp_ssl, context = setup_smtp_mocks(monkeypatch) - send_intermediate_email_with_smtp( + send_email_with_smtp( smtp_host="smtp.example.com", smtp_port=587, username="user", @@ -77,11 +77,11 @@ def test_send_intermediate_email_with_smtp_tls(monkeypatch): context.sendmail.assert_called_once() -def test_send_intermediate_email_with_smtp_ssl(monkeypatch): +def test_send_email_with_smtp_ssl(monkeypatch): email = make_basic_email() mock_smtp, mock_smtp_ssl, context = setup_smtp_mocks(monkeypatch) - send_intermediate_email_with_smtp( + send_email_with_smtp( smtp_host="smtp.example.com", smtp_port=465, username="user", @@ -95,14 +95,14 @@ def test_send_intermediate_email_with_smtp_ssl(monkeypatch): context.sendmail.assert_called_once() -def test_send_intermediate_email_with_smtp_with_attachment(monkeypatch): +def test_send_email_with_smtp_with_attachment(monkeypatch): email = make_basic_email() email.external_attachments = ["file.txt"] mock_smtp, mock_smtp_ssl, context = setup_smtp_mocks(monkeypatch) with patch("builtins.open", mock_open(read_data=b"data")): - send_intermediate_email_with_smtp( + send_email_with_smtp( smtp_host="smtp.example.com", smtp_port=587, username="user", @@ -117,14 +117,14 @@ def test_send_intermediate_email_with_smtp_with_attachment(monkeypatch): assert 'Content-Disposition: attachment; filename="file.txt"' in email_message -def test_send_intermediate_email_with_smtp_unknown_mime_type(monkeypatch): +def test_send_email_with_smtp_unknown_mime_type(monkeypatch): email = make_basic_email() email.external_attachments = ["file_without_extension"] mock_smtp, mock_smtp_ssl, context = setup_smtp_mocks(monkeypatch) with patch("builtins.open", mock_open(read_data=b"data")): - send_intermediate_email_with_smtp( + send_email_with_smtp( smtp_host="smtp.example.com", smtp_port=587, username="user", @@ -144,11 +144,11 @@ def test_send_intermediate_email_with_smtp_unknown_mime_type(monkeypatch): ) -def test_send_intermediate_email_with_smtp_sendmail_args(monkeypatch): +def test_send_email_with_smtp_sendmail_args(monkeypatch): email = make_basic_email() mock_smtp, mock_smtp_ssl, context = setup_smtp_mocks(monkeypatch) - send_intermediate_email_with_smtp( + send_email_with_smtp( smtp_host="mock_host", smtp_port=465, username="user@gmail.com", @@ -175,16 +175,16 @@ def test_send_intermediate_email_with_smtp_sendmail_args(monkeypatch): # this is probably not the best way to test this, # for what it's worth I will test each part separately def test_send_quarto_email_with_gmail(monkeypatch): - # Mock the quarto_json_to_intermediate_email function + # Mock the quarto_json_to_email function mock_quarto_to_email = MagicMock(return_value=make_basic_email()) monkeypatch.setattr( - "emailer_lib.egress.quarto_json_to_intermediate_email", mock_quarto_to_email + "nbmail.egress.quarto_json_to_email", mock_quarto_to_email ) # Mock the Gmail sending function mock_send_gmail = MagicMock() monkeypatch.setattr( - "emailer_lib.egress.send_intermediate_email_with_gmail", mock_send_gmail + "nbmail.egress.send_email_with_gmail", mock_send_gmail ) # Call the function @@ -203,7 +203,7 @@ def test_send_quarto_email_with_gmail(monkeypatch): assert i_email.recipients == ["recipient@example.com"] -def test_send_intermediate_email_with_mailgun(monkeypatch): +def test_send_email_with_mailgun(monkeypatch): email = make_basic_email() email.external_attachments = ["file.txt"] @@ -225,7 +225,7 @@ def test_send_intermediate_email_with_mailgun(monkeypatch): with patch("mailgun.client.Client", mock_client_class): with patch("builtins.open", mock_open(read_data=b"file content")): - response = send_intermediate_email_with_mailgun( + response = send_email_with_mailgun( api_key="test-api-key", domain="mg.example.com", sender="sender@example.com", @@ -259,15 +259,15 @@ def test_send_intermediate_email_with_mailgun(monkeypatch): } -def test_send_intermediate_email_with_mailgun_no_recipients(): - email = IntermediateEmail( +def test_send_email_with_mailgun_no_recipients(): + email = Email( html="Hi
", subject="Test", recipients=None, ) with pytest.raises(TypeError, match="i_email must have a populated recipients attribute"): - send_intermediate_email_with_mailgun( + send_email_with_mailgun( api_key="test-api-key", domain="mg.example.com", sender="sender@example.com", @@ -278,8 +278,8 @@ def test_send_intermediate_email_with_mailgun_no_recipients(): @pytest.mark.parametrize( "send_func", [ - send_intermediate_email_with_redmail, - send_intermediate_email_with_yagmail, + send_email_with_redmail, + send_email_with_yagmail, ], ) def test_not_implemented_functions(send_func): diff --git a/emailer_lib/tests/test_end_to_end.py b/nbmail/tests/test_end_to_end.py similarity index 90% rename from emailer_lib/tests/test_end_to_end.py rename to nbmail/tests/test_end_to_end.py index 7c01906..29a6175 100644 --- a/emailer_lib/tests/test_end_to_end.py +++ b/nbmail/tests/test_end_to_end.py @@ -2,8 +2,8 @@ import time from aiosmtpd.controller import Controller from email import message_from_bytes -from emailer_lib.egress import send_intermediate_email_with_smtp -from emailer_lib.structs import IntermediateEmail +from nbmail.egress import send_email_with_smtp +from nbmail.structs import Email class EmailHandler: @@ -37,7 +37,7 @@ def smtp_server(): def test_send_email_integration(smtp_server): controller, handler = smtp_server - email = IntermediateEmail( + email = Email( html="Hello World
", subject="Integration Test", recipients=["test@example.com"], @@ -47,7 +47,7 @@ def test_send_email_integration(smtp_server): ) # Send email to the test SMTP server - send_intermediate_email_with_smtp( + send_email_with_smtp( smtp_host="127.0.0.1", smtp_port=8025, username="test@example.com", @@ -87,4 +87,4 @@ def test_send_email_integration(smtp_server): inline_images = [p for p in msg.walk() if p.get('Content-ID')] assert len(inline_images) == 1 - assert 'HTML content
", subtype="html") - result = _email_message_to_intermediate_email(msg) + result = _email_message_to_email(msg) assert result.subject == "Test Subject" assert result.recipients == ["recipient@example.com"] @@ -30,7 +30,7 @@ def test_email_message_to_intermediate_email_simple(): assert result.external_attachments is None -def test_email_message_to_intermediate_email_multiple_recipients(): +def test_email_message_to_email_multiple_recipients(): msg = EmailMessage() msg["Subject"] = "Multi-recipient" msg["To"] = "to1@example.com, to2@example.com" @@ -38,7 +38,7 @@ def test_email_message_to_intermediate_email_multiple_recipients(): msg["Bcc"] = "bcc@example.com" msg.add_alternative("Test", subtype="html") - result = _email_message_to_intermediate_email(msg) + result = _email_message_to_email(msg) assert len(result.recipients) == 4 assert "to1@example.com" in result.recipients @@ -47,7 +47,7 @@ def test_email_message_to_intermediate_email_multiple_recipients(): assert "bcc@example.com" in result.recipients -def test_email_message_to_intermediate_email_with_inline_image(): +def test_email_message_to_email_with_inline_image(): msg = EmailMessage() msg["Subject"] = "With Image" msg.add_alternative("Quarto email
", "rsc_email_subject": "Quarto Test", @@ -233,7 +233,7 @@ def test_quarto_json_to_intermediate_email_basic(tmp_path): with open(json_file, "w") as f: json.dump(json_data, f) - result = quarto_json_to_intermediate_email(str(json_file)) + result = quarto_json_to_email(str(json_file)) assert result.subject == "Quarto Test" assert "Quarto email
" in result.html @@ -244,7 +244,7 @@ def test_quarto_json_to_intermediate_email_basic(tmp_path): assert result.rsc_email_supress_scheduled is False -def test_quarto_json_to_intermediate_email_minimal(tmp_path): +def test_quarto_json_to_email_minimal(tmp_path): json_data = { "rsc_email_body_html": "Minimal", "rsc_email_subject": "Minimal Subject", @@ -254,7 +254,7 @@ def test_quarto_json_to_intermediate_email_minimal(tmp_path): with open(json_file, "w") as f: json.dump(json_data, f) - result = quarto_json_to_intermediate_email(str(json_file)) + result = quarto_json_to_email(str(json_file)) assert result.subject == "Minimal Subject" assert result.html == "Minimal" @@ -265,7 +265,7 @@ def test_quarto_json_to_intermediate_email_minimal(tmp_path): assert result.rsc_email_supress_scheduled is False -def test_quarto_json_to_intermediate_email_empty_lists(tmp_path): +def test_quarto_json_to_email_empty_lists(tmp_path): """Test handling empty lists for attachments and images.""" json_data = { "rsc_email_body_html": "Test", @@ -279,7 +279,7 @@ def test_quarto_json_to_intermediate_email_empty_lists(tmp_path): with open(json_file, "w") as f: json.dump(json_data, f) - result = quarto_json_to_intermediate_email(str(json_file)) + result = quarto_json_to_email(str(json_file)) assert result.external_attachments == [] assert result.inline_attachments == {} diff --git a/emailer_lib/tests/test_structs.py b/nbmail/tests/test_structs.py similarity index 94% rename from emailer_lib/tests/test_structs.py rename to nbmail/tests/test_structs.py index a5876f7..60d9847 100644 --- a/emailer_lib/tests/test_structs.py +++ b/nbmail/tests/test_structs.py @@ -1,11 +1,11 @@ import re import pytest -from emailer_lib.structs import IntermediateEmail +from nbmail.structs import Email def test_creation_with_text_and_attachments(): - email = IntermediateEmail( + email = Email( html="Hi
", subject="With Text and Attachments", text="Plain text version", @@ -21,7 +21,7 @@ def test_creation_with_text_and_attachments(): def test_creation_without_text_and_attachments(): - email = IntermediateEmail( + email = Email( html="Hi
", subject="No Text or Attachments", ) @@ -34,7 +34,7 @@ def test_creation_without_text_and_attachments(): def test_subject_inserts_after_body(tmp_path): html = "Hello!
" - email = IntermediateEmail( + email = Email( html=html, subject="Test Subject", rsc_email_supress_report_attachment=False, @@ -55,7 +55,7 @@ def test_subject_inserts_after_body(tmp_path): def test_subject_prepends_if_no_body(tmp_path): html = "Hello!
" - email = IntermediateEmail( + email = Email( html=html, subject="NoBody", ) @@ -68,7 +68,7 @@ def test_subject_prepends_if_no_body(tmp_path): def test_raises_on_external_attachments(tmp_path): html = "Test
" - email = IntermediateEmail( + email = Email( html=html, subject="Test", external_attachments=["file.txt"], @@ -88,7 +88,7 @@ def test_raises_on_external_attachments(tmp_path): ) def test_not_implemented_methods(method_name): """Test that unimplemented methods raise NotImplementedError.""" - email = IntermediateEmail( + email = Email( html="Hi
", subject="Test", ) @@ -99,7 +99,7 @@ def test_not_implemented_methods(method_name): def test_preview_email_simple_html(tmp_path, snapshot): html = "Hello World!
" - email = IntermediateEmail( + email = Email( html=html, subject="Simple Test Email", ) @@ -120,7 +120,7 @@ def test_preview_email_with_inline_attachments(tmp_path, snapshot):