-
Notifications
You must be signed in to change notification settings - Fork 336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ENG-6682] Add reply-to and cc-ing features to Institutional Access #10841
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,10 @@ def send_email( | |
to_addr: str, | ||
subject: str, | ||
message: str, | ||
reply_to: bool = False, | ||
ttls: bool = True, | ||
login: bool = True, | ||
cc_addr: [] = None, | ||
username: str = None, | ||
password: str = None, | ||
categories=None, | ||
|
@@ -57,6 +59,8 @@ def send_email( | |
categories=categories, | ||
attachment_name=attachment_name, | ||
attachment_content=attachment_content, | ||
reply_to=reply_to, | ||
cc_addr=cc_addr, | ||
) | ||
else: | ||
return _send_with_smtp( | ||
|
@@ -68,35 +72,61 @@ def send_email( | |
login=login, | ||
username=username, | ||
password=password, | ||
reply_to=reply_to, | ||
cc_addr=cc_addr, | ||
) | ||
|
||
|
||
def _send_with_smtp(from_addr, to_addr, subject, message, ttls=True, login=True, username=None, password=None): | ||
def _send_with_smtp( | ||
from_addr, | ||
to_addr, | ||
subject, | ||
message, | ||
ttls=True, | ||
login=True, | ||
username=None, | ||
password=None, | ||
cc_addr=None, | ||
reply_to=None, | ||
): | ||
username = username or settings.MAIL_USERNAME | ||
password = password or settings.MAIL_PASSWORD | ||
|
||
if login and (username is None or password is None): | ||
logger.error('Mail username and password not set; skipping send.') | ||
return | ||
return False | ||
|
||
msg = MIMEText(message, 'html', _charset='utf-8') | ||
msg = MIMEText( | ||
message, | ||
'html', | ||
_charset='utf-8', | ||
) | ||
msg['Subject'] = subject | ||
msg['From'] = from_addr | ||
msg['To'] = to_addr | ||
|
||
s = smtplib.SMTP(settings.MAIL_SERVER) | ||
s.ehlo() | ||
if ttls: | ||
s.starttls() | ||
s.ehlo() | ||
if login: | ||
s.login(username, password) | ||
s.sendmail( | ||
from_addr=from_addr, | ||
to_addrs=[to_addr], | ||
msg=msg.as_string(), | ||
) | ||
s.quit() | ||
if cc_addr: | ||
msg['Cc'] = ', '.join(cc_addr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check with product: How do they want the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah Product agrees BCC is better. |
||
|
||
if reply_to: | ||
msg['Reply-To'] = reply_to | ||
|
||
# Combine recipients for SMTP | ||
recipients = [to_addr] + (cc_addr or []) | ||
|
||
# Establish SMTP connection and send the email | ||
with smtplib.SMTP(settings.MAIL_SERVER) as server: | ||
server.ehlo() | ||
if ttls: | ||
server.starttls() | ||
server.ehlo() | ||
if login: | ||
server.login(username, password) | ||
server.sendmail( | ||
from_addr=from_addr, | ||
to_addrs=recipients, | ||
msg=msg.as_string() | ||
) | ||
return True | ||
|
||
|
||
|
@@ -108,39 +138,59 @@ def _send_with_sendgrid( | |
categories=None, | ||
attachment_name: str = None, | ||
attachment_content=None, | ||
cc_addr=None, | ||
reply_to=None, | ||
client=None, | ||
): | ||
if ( | ||
settings.SENDGRID_WHITELIST_MODE and to_addr in settings.SENDGRID_EMAIL_WHITELIST | ||
) or settings.SENDGRID_WHITELIST_MODE is False: | ||
client = client or SendGridAPIClient(settings.SENDGRID_API_KEY) | ||
mail = Mail(from_email=from_addr, html_content=message, to_emails=to_addr, subject=subject) | ||
if categories: | ||
mail.category = [Category(x) for x in categories] | ||
if attachment_name and attachment_content: | ||
content_bytes = _content_to_bytes(attachment_content) | ||
content_bytes = FileContent(b64encode(content_bytes).decode()) | ||
attachment = Attachment(file_content=content_bytes, file_name=attachment_name) | ||
mail.add_attachment(attachment) | ||
|
||
response = client.send(mail) | ||
if response.status_code >= 400: | ||
sentry.log_message( | ||
f'{response.status_code} error response from sendgrid.' | ||
f'from_addr: {from_addr}\n' | ||
f'to_addr: {to_addr}\n' | ||
f'subject: {subject}\n' | ||
'mimetype: html\n' | ||
f'message: {response.body[:30]}\n' | ||
f'categories: {categories}\n' | ||
f'attachment_name: {attachment_name}\n' | ||
) | ||
return response.status_code < 400 | ||
else: | ||
in_allowed_list = to_addr in settings.SENDGRID_EMAIL_WHITELIST | ||
if settings.SENDGRID_WHITELIST_MODE and not in_allowed_list: | ||
sentry.log_message( | ||
f'SENDGRID_WHITELIST_MODE is True. Failed to send emails to non-whitelisted recipient {to_addr}.' | ||
) | ||
return False | ||
|
||
client = client or SendGridAPIClient(settings.SENDGRID_API_KEY) | ||
mail = Mail( | ||
from_email=from_addr, | ||
html_content=message, | ||
to_emails=to_addr, | ||
subject=subject | ||
) | ||
|
||
if reply_to: # Add Reply-To header if provided | ||
mail.reply_to = [{'email': reply_to}] | ||
|
||
if cc_addr: # Add CC header if CC addresses exist | ||
mail.add_cc([{'email': email} for email in cc_addr]) | ||
|
||
if categories: | ||
mail.category = [Category(x) for x in categories] | ||
|
||
if attachment_name and attachment_content: | ||
attachment = Attachment( | ||
file_content=_content_to_bytes( | ||
FileContent( | ||
b64encode(attachment_content).decode() | ||
) | ||
), | ||
file_name=attachment_name | ||
) | ||
mail.add_attachment(attachment) | ||
|
||
response = client.send(mail) | ||
if response.status_code not in (200, 201): | ||
sentry.log_message( | ||
f'{response.status_code} error response from sendgrid.' | ||
f'from_addr: {from_addr}\n' | ||
f'to_addr: {to_addr}\n' | ||
f'subject: {subject}\n' | ||
'mimetype: html\n' | ||
f'message: {response.body[:30]}\n' | ||
f'categories: {categories}\n' | ||
f'attachment_name: {attachment_name}\n' | ||
) | ||
else: | ||
return True | ||
|
||
def _content_to_bytes(attachment_content: BytesIO | str | bytes) -> bytes: | ||
if isinstance(attachment_content, bytes): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
from django.db import models | ||
from django.db.models.signals import post_save | ||
from django.dispatch import receiver | ||
|
||
from .base import BaseModel, ObjectIDMixin | ||
from website.mails import send_mail, USER_MESSAGE_INSTITUTIONAL_ACCESS_REQUEST | ||
|
||
|
@@ -70,14 +71,24 @@ class UserMessage(BaseModel, ObjectIDMixin): | |
on_delete=models.CASCADE, | ||
help_text='The institution associated with this message.' | ||
) | ||
is_sender_CCed = models.BooleanField( | ||
default=False, | ||
help_text='The boolean value that indicates whether other institutional admins were CCed', | ||
) | ||
reply_to = models.BooleanField( | ||
default=False, | ||
help_text='Whether to set the sender\'s username as the `Reply-To` header in the email.' | ||
) | ||
|
||
def send_institution_request(self) -> None: | ||
""" | ||
Sends an institutional access request email to the recipient of the message. | ||
""" | ||
send_mail( | ||
mail=MessageTypes.get_template(self.message_type), | ||
to_addr=self.recipient.username, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, in the case of BCC'ing the sender, the recipient also needs to be BCC'd. There would be no to_address in that case. Without the BCC, however, it makes sense to have the recipient be in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a wrapper for the the two different email utilities that implement the BBC in different ways, so it's hard to say which behavior is actually "the best". But anyway, if there's no There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm not super-concerned where it's changed, just that all recipients of the email are bcc'd if the bcc option is chosen. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I dealt with that here:
https://github.com/CenterForOpenScience/osf.io/pull/10841/files#diff-78b71751c095a6eb9a92be212c4acf17f402fc34266030b5440596cf2a57d243R112 sorry probably got lost with the clean-up, but that's new. Sendgrid has it's own way of handling it. SMTP BCCs by just not CCing, so no additional action is necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool cool |
||
mail=MessageTypes.get_template(MessageTypes.INSTITUTIONAL_REQUEST), | ||
cc_addr=[self.sender.username] if self.is_sender_CCed else None, | ||
reply_to=self.sender.username if self.reply_to else None, | ||
user=self.recipient, | ||
**{ | ||
'sender': self.sender, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be handled later on, but in the case of a bcc, both the sender and the recipient should be bcc'd, otherwise the sender will be able to see the recipient's email address.