mail
¶
Functionality around mailing
ToDo
- add logging where appropriate
- Find out why
extra=Extra.allow
causes mypy to fail. Seems like a bug in pydantic. - Sending mails is quite slow, so using
tqdm
to show feedback to the current progress would be nice
Mail
¶
Mail template
Use the data
field to store additional information
You can use the typical Format String Syntax and the objects recipient
and mail
to access metadata to complement the template, e.g.:
Hello {recipient.address_as},
We hope it's ok to address you your first name rather than using your full name being {recipient.name}.
Have you read the email's subject '{mail.subject}'? How is your work right now at {recipient.data.company}?
Cheers!
agent_id: str
instance-attribute
¶
data: MetaData | None = None
class-attribute
instance-attribute
¶
recipients: list[Recipient]
instance-attribute
¶
status: str = 'solved'
class-attribute
instance-attribute
¶
subject: str
instance-attribute
¶
team_id: str
instance-attribute
¶
text: str
instance-attribute
¶
MailClient(helpdesk_client: HelpDeskClient | None = None)
¶
Mail client for mass mails over HelpDesk
Source code in src/pytanis/helpdesk/mail.py
def __init__(self, helpdesk_client: HelpDeskClient | None = None):
if helpdesk_client is None:
helpdesk_client = HelpDeskClient()
self._helpdesk_client = helpdesk_client
self.dry_run: Callable[[NewTicket], None] = self.print_new_ticket
batch_size: int = 10
class-attribute
instance-attribute
¶
dry_run: Callable[[NewTicket], None] = self.print_new_ticket
instance-attribute
¶
wait_time: int = 20
class-attribute
instance-attribute
¶
print_new_ticket(ticket: NewTicket)
staticmethod
¶
Default action in a dry-run. Mainly for making sure you sent what you mean!
Overwrite it by assigning to self.dry_run another function
ToDo: Make this function nice, maybe use the rich
library even
Source code in src/pytanis/helpdesk/mail.py
@staticmethod
def print_new_ticket(ticket: NewTicket):
"""Default action in a dry-run. Mainly for making sure you sent what you mean!
Overwrite it by assigning to self.dry_run another function
ToDo: Make this function nice, maybe use the `rich` library even
"""
print('#' * 40) # noqa: T201
print(f'Recipient: {ticket.requester.name} <{ticket.requester.email}>') # noqa: T201
print(f'Subject: {ticket.subject}') # noqa: T201
print(f'{ticket.message.text}') # noqa: T201
send(mail: Mail, *, dry_run: bool = True) -> tuple[list[tuple[Recipient, Ticket | None]], list[tuple[Recipient, Exception]]]
¶
Send a mail to all recipients using HelpDesk
Source code in src/pytanis/helpdesk/mail.py
def send(
self, mail: Mail, *, dry_run: bool = True
) -> tuple[list[tuple[Recipient, Ticket | None]], list[tuple[Recipient, Exception]]]:
"""Send a mail to all recipients using HelpDesk"""
errors = []
tickets = []
for idx, recipient in enumerate(tqdm(mail.recipients), start=1):
recip_mail = mail.model_copy()
try:
recip_mail.subject = mail.subject.format(recipient=recipient, mail=mail)
# be aware here that the body might reference to subject line, so it must be filled already
recip_mail.text = recip_mail.text.format(recipient=recipient, mail=recip_mail)
ticket = self._create_ticket(recip_mail, recipient)
if dry_run:
self.print_new_ticket(ticket)
resp_ticket = None
else:
resp = self._helpdesk_client.create_ticket(ticket)
resp_ticket = Ticket.model_validate(resp)
except Exception as e:
errors.append((recipient, e))
else:
tickets.append((recipient, resp_ticket))
if (idx % self.batch_size == 0) and not dry_run:
time.sleep(self.wait_time)
return tickets, errors
MetaData
¶
Additional, arbitrary metadata provided by the user like for template filling
model_config = ConfigDict(extra='allow')
class-attribute
instance-attribute
¶
Recipient
¶
Details about the recipient
Use the data
field to store additional information
address_as: str | None = None
class-attribute
instance-attribute
¶
data: MetaData | None = None
class-attribute
instance-attribute
¶
email: str
instance-attribute
¶
name: str
instance-attribute
¶
fill_with_name(v, values)
classmethod
¶
Source code in src/pytanis/helpdesk/mail.py
@validator('address_as')
@classmethod
def fill_with_name(cls, v, values):
if v is None:
v = values['name']
return v