Coder Social home page Coder Social logo

prefect-email's Introduction

Note

Active development of this project has moved within PrefectHQ/prefect. The code can be found here and documentation here. Please open issues and PRs against PrefectHQ/prefect instead of this repository.

prefect-email


PyPI

Visit the full docs here to see additional examples and the API reference.

prefect-email is a collection of prebuilt Prefect integrations that can be used to interact with email services.

Getting Started

Integrate with Prefect flows

prefect-email makes sending emails effortless, giving you peace of mind that your emails are being sent as expected.

First, install prefect-email and save your email credentials to a block to run the examples below!

from prefect import flow
from prefect_email import EmailServerCredentials, email_send_message

@flow
def example_email_send_message_flow(email_addresses):
    email_server_credentials = EmailServerCredentials.load("BLOCK-NAME-PLACEHOLDER")
    for email_address in email_addresses:
        subject = email_send_message.with_options(name=f"email {email_address}").submit(
            email_server_credentials=email_server_credentials,
            subject="Example Flow Notification using Gmail",
            msg="This proves email_send_message works!",
            email_to=email_address,
        )

example_email_send_message_flow(["EMAIL-ADDRESS-PLACEHOLDER"])

Outputs:

16:58:27.646 | INFO    | prefect.engine - Created flow run 'busy-bat' for flow 'example-email-send-message-flow'
16:58:29.225 | INFO    | Flow run 'busy-bat' - Created task run 'email [email protected]' for task 'email [email protected]'
16:58:29.229 | INFO    | Flow run 'busy-bat' - Submitted task run 'email [email protected]' for execution.
16:58:31.523 | INFO    | Task run 'email [email protected]' - Finished in state Completed()
16:58:31.713 | INFO    | Flow run 'busy-bat' - Finished in state Completed('All states completed.')

Please note, many email services, like Gmail, require an App Password to successfully send emails. If you encounter an error similar to smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted..., it's likely you are not using an App Password.

Capture exceptions and notify by email

Perhaps you want an email notification with the details of the exception when your flow run fails.

prefect-email can be wrapped in an except statement to do just that!

from prefect import flow
from prefect.context import get_run_context
from prefect_email import EmailServerCredentials, email_send_message

def notify_exc_by_email(exc):
    context = get_run_context()
    flow_run_name = context.flow_run.name
    email_server_credentials = EmailServerCredentials.load("email-server-credentials")
    email_send_message(
        email_server_credentials=email_server_credentials,
        subject=f"Flow run {flow_run_name!r} failed",
        msg=f"Flow run {flow_run_name!r} failed due to {exc}.",
        email_to=email_server_credentials.username,
    )

@flow
def example_flow():
    try:
        1 / 0
    except Exception as exc:
        notify_exc_by_email(exc)
        raise

example_flow()

Resources

For more tips on how to use tasks and flows in a Collection, check out Using Collections!

Installation

Install prefect-email with pip:

pip install prefect-email

Then, register to view the block on Prefect Cloud:

prefect block register -m prefect_email

Note, to use the load method on Blocks, you must already have a block document saved through code or saved through the UI.

Requires an installation of Python 3.7+.

We recommend using a Python virtual environment manager such as pipenv, conda or virtualenv.

These tasks are designed to work with Prefect 2. For more information about how to use Prefect, please refer to the Prefect documentation.

Saving credentials to block

Note, to use the load method on Blocks, you must already have a block document saved through code or saved through the UI.

Below is a walkthrough on saving block documents through code.

Create a short script, replacing the placeholders.

from prefect_email import EmailServerCredentials

credentials = EmailServerCredentials(
    username="EMAIL-ADDRESS-PLACEHOLDER",
    password="PASSWORD-PLACEHOLDER",  # must be an app password
)
credentials.save("BLOCK-NAME-PLACEHOLDER")

Congrats! You can now easily load the saved block, which holds your credentials:

from prefect_email import EmailServerCredentials

EmailServerCredentials.load("BLOCK_NAME_PLACEHOLDER")

!!! info "Registering blocks"

Register blocks in this module to
[view and edit them](https://orion-docs.prefect.io/ui/blocks/)
on Prefect Cloud:

```bash
prefect block register -m prefect_email
```

A list of available blocks in prefect-email and their setup instructions can be found here.

Feedback

If you encounter any bugs while using prefect-email, feel free to open an issue in the prefect-email repository.

If you have any questions or issues while using prefect-email, you can find help in either the Prefect Discourse forum or the Prefect Slack community.

Feel free to star or watch prefect-email for updates too!

Contributing

If you'd like to help contribute to fix an issue or add a feature to prefect-email, please propose changes through a pull request from a fork of the repository.

Here are the steps:

  1. Fork the repository
  2. Clone the forked repository
  3. Install the repository and its dependencies:
pip install -e ".[dev]"
  1. Make desired changes
  2. Add tests
  3. Insert an entry to CHANGELOG.md
  4. Install pre-commit to perform quality checks prior to commit:
pre-commit install
  1. git commit, git push, and create a pull request

prefect-email's People

Contributors

ahuang11 avatar chrisguidry avatar cicdw avatar dependabot[bot] avatar desertaxle avatar discdiver avatar drfraser avatar prefect-collection-synchronizer[bot] avatar urimandujano avatar zhen0 avatar zzstoatzz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

prefect-email's Issues

SSL and STARTTLS are not equivalent

In the SMTPType enum, SSL and STARTTLS are both 465 which causes the code in EmailServerCredentials.get_server() to always create a server of type SMTP_SSL (the next two lines will get skipped) [ or vice versa ]

This means if you have a mail server using STARTTLS on port 587, the code will never connect properly, i.e. starttls() never gets called. STARTTLS ought to be port 587 by default. 465 is SSL/TLS

Not sure how to exclude this new change from the existing ones, so did not make the change in my repo yet

SMTP Enum is saved instead of Name on Email Server Credentials block

Utilizing the Email Server credentials block results in an error when the SMTP Type enumeration is specified

from prefect_email import EmailServerCredentials, SMTPType

email_server_credentials = EmailServerCredentials(
    smtp_server="[us-smtp-outbound-1.mimecast.com](https://us-smtp-outbound-1.mimecast.com/)",
    smtp_type=SMTPType.STARTTLS,
    smtp_port=587,
    username="username",
    password="password"
)
email_server_credentials.save("email-credentials3", overwrite=True)
creds = EmailServerCredentials.load("email-credentials3")
print(creds.smtp_type)

Whereas if the a string is used to specify the SMTP type the block functions correctly

email_server_credentials = EmailServerCredentials(
        smtp_server="[us-smtp-outbound-1.mimecast.com](https://us-smtp-outbound-1.mimecast.com/)",
        smtp_type="STARTTLS",
        smtp_port=587,
        username="username",
        password="password"
    )

    email_server_credentials.save("email-credentials3", overwrite=True)

    creds = EmailServerCredentials.load("email-credentials3")
    print(creds.smtp_type)

Utilizing the block returns this error:

Encountered exception during execution: Traceback (most recent call last): File "/usr/local/lib/python3.10/site-packages/prefect/[engine.py](https://engine.py/)", line 1478, in orchestrate_task_run result = await task.fn(*args, **kwargs) File "/usr/local/lib/python3.10/site-packages/prefect_email/[message.py](https://message.py/)", line 116, in email_send_message with email_server_credentials.get_server() as server: File "/usr/local/lib/python3.10/site-packages/prefect_email/[credentials.py](https://credentials.py/)", line 130, in get_server smtp_type = _cast_to_enum(self.smtp_type, SMTPType, restrict=True) File "/usr/local/lib/python3.10/site-packages/prefect_email/[credentials.py](https://credentials.py/)", line 57, in _cast_to_enum raise ValueError(f"Must be one of {valid_enums}; got {obj!r}") ValueError: Must be one of ['SSL', 'STARTTLS', 'INSECURE']; got '587'

Add collection sync workflow using cruft

Add cruft to repo to allow synchronization of this collection with the original template.
Cruft can be added by running cruft link. Note that a starting commit will need to be specified.
Using the commit of the prefect-collection-template closest to the generation date of this repo
is a good default.

Unable to set Smtp Type via UI

When setting up the EmailServerCredentials block via the UI, the field Smtp Type accepts only JSON type. Therefore the value must be supplied as quoted string, e.g. "INSECURE".
This results however in the following error message when trying to send a message:

ValueError: Must be one of ['SSL', 'STARTTLS', 'INSECURE']; got '"INSECURE"'

When the input field is left empty, the error message looks as follows:

ValueError: Must be one of ['SSL', 'STARTTLS', 'INSECURE']; got ''

username and password mandatory even if EmailServerCredentials is using an INSECURE server

Username and password should be set as Optional as they aren't required if your code is connecting to a server not requiring login information. And what if things are set up such that SSL is used to secure the connection (e.g. server and client certificates used as authentication), but no account info is needed? Then server.login() shouldn't be called (line 146 in credentials.py). Unusual perhaps but technically possible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.