Skip to content

Emails

Emails(baze)

Class used for handling emails.

Source code in echo_baze/baze_root.py
def __init__(self, baze: e_bz.Baze) -> None:
    """Base class that all subclasses should inherit from.

    Parameters
    ----------
    baze : Baze
        Top level object carrying all functionality and the connection handler.

    """
    # check inputs
    if not isinstance(baze, e_bz.Baze):
        raise ValueError(f"baze must be of type Baze, not {type(baze)}")

    self.baze: e_bz.Baze = baze

send(subject, message, users=None, roles=None, emails=None, files=None, recipient_errors='ignore')

Sends an email using Bazefield bulk email API. This feature can be accessed in the portal under Administration > Bulk Email.

Parameters:

  • subject

    (str) –

    Subject of the email.

  • message

    (str) –

    Body of the email. Can be formatted with HTML. All \n will be replaced with
    automatically.

  • users

    (list[str] | None, default: None ) –

    List of user names to send emails to. By default None

  • roles

    (list[str] | None, default: None ) –

    List of role names to send emails to. By default None

  • emails

    (list[EmailStr] | None, default: None ) –

    List of email addresses to send emails to. By default None

  • files

    (dict[str, Path] | None, default: None ) –

    Dict containing the files to be sent. Must be in the format {file_name: file_path}, where file_name includes the extension. If set to None, no attachments will be added. By default None

  • recipient_errors

    (Literal['ignore', 'raise'], default: 'ignore' ) –

    What to do if one of the recipients is not found.

    If "ignore", the email will be sent to the other recipients.

    If "raise", a ValueError will be raised.

    By default "ignore"

Source code in echo_baze/emails.py
@validate_call
def send(
    self,
    subject: str,
    message: str,
    users: list[str] | None = None,
    roles: list[str] | None = None,
    emails: list[EmailStr] | None = None,
    files: dict[str, Path] | None = None,
    recipient_errors: Literal["ignore", "raise"] = "ignore",
) -> None:
    r"""Sends an email using Bazefield bulk email API. This feature can be accessed in the portal under `Administration > Bulk Email`.

    Parameters
    ----------
    subject : str
        Subject of the email.
    message : str
        Body of the email. Can be formatted with HTML.
        All \n will be replaced with <br> automatically.
    users : list[str] | None, optional
        List of user names to send emails to. By default None
    roles : list[str] | None, optional
        List of role names to send emails to. By default None
    emails : list[EmailStr] | None, optional
        List of email addresses to send emails to. By default None
    files : dict[str, Path] | None, optional
        Dict containing the files to be sent. Must be in the format {file_name: file_path}, where file_name includes the extension.
        If set to None, no attachments will be added.
        By default None
    recipient_errors : Literal["ignore", "raise"], optional
        What to do if one of the recipients is not found.

        If "ignore", the email will be sent to the other recipients.

        If "raise", a ValueError will be raised.

        By default "ignore"

    """
    # checking if there is at least one user or role
    if users is None and roles is None and emails is None:
        raise ValueError("Please specify at least one role, user or email!")
    if (
        (users is not None and len(users) == 0)
        and (roles is not None and len(roles) == 0)
        and (emails is not None and len(emails)) == 0
    ):
        raise ValueError("Please specify at least one role, user or email!")

    # getting users and role ids
    user_ids = self.baze.users.get_ids()
    role_ids = self.baze.roles.get_ids()

    if recipient_errors == "raise":
        if users is not None and (wrong_users := [user for user in users if user not in user_ids]):
            raise ValueError(f"Users {wrong_users} do not exist!")
        if roles is not None and (wrong_roles := [role for role in roles if role not in role_ids]):
            raise ValueError(f"Roles {wrong_roles} do not exist!")

    # filtering users and roles
    user_ids = [user_id for user_name, user_id in user_ids.items() if users is not None and user_name in users]
    role_ids = [role_id for role_name, role_id in role_ids.items() if roles is not None and role_name in roles]

    # raising error if no users or roles are found
    if not user_ids and not role_ids and not emails:
        raise ValueError("No users or roles found!")

    # checking if all files exist
    if files is not None:
        for file_name in files:
            if not isinstance(files[file_name], Path):
                files[file_name] = Path(files[file_name])
            if not files[file_name].is_file():
                raise ValueError(f"File '{file_name}' with path '{files[file_name]}' does not exist!")

    # replacing newlines with <br>
    message = message.replace("\n", "<br>")
    # URL encoding
    message = quote(message)

    # creating the payload
    payload = {
        "UserIds": json.dumps(user_ids),
        "RoleIds": json.dumps(role_ids),
        "Recipients": json.dumps(emails if emails is not None else []),
        "Subject": subject,
        "Message": message,
        "IsHtml": "true",
        "UrlEncoded": "true",
        "attachmentUrls": "",
    }

    # adding files to payload
    if files is not None:
        for i, t in enumerate(files.items()):
            file_name, file_path = t
            mime_type, _ = mimetypes.guess_type(file_path)
            if mime_type is None:
                raise TypeError(f"Could not guess MIME type for file {file_path}.")
            payload[f"File{i}"] = (file_name, open(file_path, "rb"), mime_type)  # noqa

    mp_encoder = MultipartEncoder(fields=payload)

    # sending request
    response = self.baze.conn.post(
        endpoint="email",
        data=mp_encoder.to_string(),  # The MultipartEncoder is posted as data, don't use files=...!
        headers={"Content-Type": mp_encoder.content_type},  # The MultipartEncoder provides the content-type header with the boundary
    )
    self._handle_http_errors(response)

    logger.info(f"Email sent to users {users} and roles {roles}")