def send_login_mail(self, subject, template, add_args, now=None): """send mail with login details""" from frappe.utils.user import get_user_fullname from frappe.utils import get_url mail_titles = frappe.get_hooks().get("login_mail_title", []) title = frappe.db.get_default('company') or (mail_titles and mail_titles[0]) or "" full_name = get_user_fullname(frappe.session['user']) if full_name == "Guest": full_name = "Administrator" args = { 'first_name': self.first_name or self.last_name or "user", 'user': self.name, 'title': title, 'login_url': get_url(), 'user_fullname': full_name } args.update(add_args) sender = frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None frappe.sendmail(recipients=self.email, sender=sender, subject=subject, message=frappe.get_template(template).render(args), delayed=(not now) if now!=None else self.flags.delay_emails)
def send_login_mail(self, subject, template, add_args, now=None): """send mail with login details""" from frappe.utils.user import get_user_fullname from frappe.utils import get_url full_name = get_user_fullname(frappe.session['user']) if full_name == "Guest": full_name = "Administrator" args = { 'first_name': self.first_name or self.last_name or "user", 'user': self.name, 'title': subject, 'login_url': get_url(), 'user_fullname': full_name } args.update(add_args) sender = frappe.session.user not in STANDARD_USERS and get_formatted_email( frappe.session.user) or None frappe.sendmail( recipients=self.email, sender=sender, subject=subject, template=template, args=args, header=[subject, "green"], delayed=(not now) if now != None else self.flags.delay_emails, retry=3)
def send_login_mail(self, subject, template, add_args, now=None): """send mail with login details""" from frappe.utils.user import get_user_fullname from frappe.utils import get_url mail_titles = frappe.get_hooks().get("login_mail_title", []) title = frappe.db.get_default('company') or (mail_titles and mail_titles[0]) or "" full_name = get_user_fullname(frappe.session['user']) if full_name == "Guest": full_name = "Administrator" args = { 'first_name': self.first_name or self.last_name or "user", 'user': self.name, 'title': title, 'login_url': get_url(), 'user_fullname': full_name } args.update(add_args) sender = frappe.session.user not in STANDARD_USERS and get_formatted_email( frappe.session.user) or None frappe.sendmail( recipients=self.email, sender=sender, subject=subject, message=frappe.get_template(template).render(args), delayed=(not now) if now != None else self.flags.delay_emails, retry=3)
def send_login_mail(self, subject, template, add_args, now=None): """send mail with login details""" from frappe.utils import get_url from frappe.utils.user import get_user_fullname created_by = get_user_fullname(frappe.session["user"]) if created_by == "Guest": created_by = "Administrator" args = { "first_name": self.first_name or self.last_name or "user", "user": self.name, "title": subject, "login_url": get_url(), "created_by": created_by, } args.update(add_args) sender = ( frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None ) frappe.sendmail( recipients=self.email, sender=sender, subject=subject, template=template, args=args, header=[subject, "green"], delayed=(not now) if now != None else self.flags.delay_emails, retry=3, )
def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, extra_amount=0): credit_limit = get_credit_limit(customer, company) if not credit_limit: return customer_outstanding = get_customer_outstanding( customer, company, ignore_outstanding_sales_order) if extra_amount > 0: customer_outstanding += flt(extra_amount) if credit_limit > 0 and flt(customer_outstanding) > credit_limit: msgprint( _("Credit limit has been crossed for customer {0} ({1}/{2})"). format(customer, customer_outstanding, credit_limit)) # If not authorized person raise exception credit_controller_role = frappe.db.get_single_value( "Accounts Settings", "credit_controller") if not credit_controller_role or credit_controller_role not in frappe.get_roles( ): # form a list of emails for the credit controller users credit_controller_users = get_users_with_role( credit_controller_role or "Sales Master Manager") # form a list of emails and names to show to the user credit_controller_users_formatted = [ get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users ] if not credit_controller_users_formatted: frappe.throw( _("Please contact your administrator to extend the credit limits for {0}." ).format(customer)) message = """Please contact any of the following users to extend the credit limits for {0}: <br><br><ul><li>{1}</li></ul>""".format( customer, "<li>".join(credit_controller_users_formatted)) # if the current user does not have permissions to override credit limit, # prompt them to send out an email to the controller users frappe.msgprint( message, title="Notify", raise_exception=1, primary_action={ "label": "Send Email", "server_action": "erpnext.selling.doctype.customer.customer.send_emails", "args": { "customer": customer, "customer_outstanding": customer_outstanding, "credit_limit": credit_limit, "credit_controller_users_list": credit_controller_users, }, }, )
def get_assignees(doc): return [( get_formatted_email(d.owner) or d.owner ) for d in frappe.db.get_all("ToDo", filters={ "reference_type": doc.reference_doctype, "reference_name": doc.reference_name, "status": "Open" }, fields=["owner"]) ]
def get_assignees(self): return [( get_formatted_email(d.owner) or d.owner ) for d in frappe.db.get_all("ToDo", filters={ "reference_type": self.reference_doctype, "reference_name": self.reference_name, "status": "Open" }, fields=["owner"]) ]
def make(doctype=None, name=None, content=None, subject=None, sent_or_received="Sent", sender=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]'): """Make a new communication. :param doctype: Reference DocType. :param name: Reference Document name. :param content: Communication body. :param subject: Communication subject. :param sent_or_received: Sent or Received (default **Sent**). :param sender: Communcation sender (default current user). :param recipients: Communication recipients as list. :param communication_medium: Medium of communication (default **Email**). :param send_mail: Send via email (default **False**). :param print_html: HTML Print format to be sent as attachment. :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string.""" if doctype and name and not frappe.has_permission(doctype, "email", name): raise frappe.PermissionError( "You are not allowed to send emails related to: {doctype} {name}". format(doctype=doctype, name=name)) comm = frappe.get_doc({ "doctype": "Communication", "subject": subject, "content": content, "sender": sender or get_formatted_email(frappe.session.user), "recipients": recipients, "communication_medium": "Email", "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name }) comm.insert(ignore_permissions=True) if send_email: comm.send(print_html, print_format, attachments) return comm.name
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False, send_me_a_copy=False): """Make a new communication. :param doctype: Reference DocType. :param name: Reference Document name. :param content: Communication body. :param subject: Communication subject. :param sent_or_received: Sent or Received (default **Sent**). :param sender: Communcation sender (default current user). :param recipients: Communication recipients as list. :param communication_medium: Medium of communication (default **Email**). :param send_mail: Send via email (default **False**). :param print_html: HTML Print format to be sent as attachment. :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string. :param send_me_a_copy: Send a copy to the sender (default **False**). """ is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not ignore_doctype_permissions: raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( doctype=doctype, name=name)) if not sender and frappe.session.user != "Administrator": sender = get_formatted_email(frappe.session.user) comm = frappe.get_doc({ "doctype":"Communication", "subject": subject, "content": content, "sender": sender, "recipients": recipients, "communication_medium": "Email", "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name }) comm.insert(ignore_permissions=True) # needed for communication.notify which uses celery delay # if not committed, delayed task doesn't find the communication frappe.db.commit() recipients = None if send_email: comm.send_me_a_copy = send_me_a_copy recipients = comm.get_recipients() comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy, recipients=recipients) return { "name": comm.name, "recipients": ", ".join(recipients) if recipients else None }
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False, send_me_a_copy=False, cc=None): """Make a new communication. :param doctype: Reference DocType. :param name: Reference Document name. :param content: Communication body. :param subject: Communication subject. :param sent_or_received: Sent or Received (default **Sent**). :param sender: Communcation sender (default current user). :param recipients: Communication recipients as list. :param communication_medium: Medium of communication (default **Email**). :param send_mail: Send via email (default **False**). :param print_html: HTML Print format to be sent as attachment. :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string. :param send_me_a_copy: Send a copy to the sender (default **False**). """ is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not ignore_doctype_permissions: raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( doctype=doctype, name=name)) if not sender: sender = get_formatted_email(frappe.session.user) comm = frappe.get_doc({ "doctype":"Communication", "subject": subject, "content": content, "sender": sender, "recipients": recipients, "cc": cc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name }) comm.insert(ignore_permissions=True) # needed for communication.notify which uses celery delay # if not committed, delayed task doesn't find the communication frappe.db.commit() if send_email: comm.send_me_a_copy = send_me_a_copy comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) return { "name": comm.name, "emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None }
def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None): """Prepare to make multipart MIME Email :param print_html: Send given value as HTML attachment. :param print_format: Attach print format of parent document.""" view_link = frappe.utils.cint(frappe.db.get_value("Print Settings", "Print Settings", "attach_view_link")) if print_format and view_link: doc.content += get_attach_link(doc, print_format) set_incoming_outgoing_accounts(doc) if not doc.sender: doc.sender = doc.outgoing_email_account.email_id if not doc.sender_full_name: doc.sender_full_name = doc.outgoing_email_account.name or _("Notification") if doc.sender: # combine for sending to get the format 'Jane <*****@*****.**>' doc.sender = get_formatted_email(doc.sender_full_name, mail=doc.sender) doc.attachments = [] if print_html or print_format: doc.attachments.append({"print_format_attachment":1, "doctype":doc.reference_doctype, "name":doc.reference_name, "print_format":print_format, "html":print_html}) if attachments: if isinstance(attachments, string_types): attachments = json.loads(attachments) for a in attachments: if isinstance(a, string_types): # is it a filename? try: # check for both filename and file id file_id = frappe.db.get_list('File', or_filters={'file_name': a, 'name': a}, limit=1) if not file_id: frappe.throw(_("Unable to find attachment {0}").format(a)) file_id = file_id[0]['name'] _file = frappe.get_doc("File", file_id) _file.get_content() # these attachments will be attached on-demand # and won't be stored in the message doc.attachments.append({"fid": file_id}) except IOError: frappe.throw(_("Unable to find attachment {0}").format(a)) else: doc.attachments.append(a)
def check_credit_limit(customer, company, reference_doctype, reference_document, ignore_outstanding_sales_order=False, extra_amount=0): customer_outstanding = get_customer_outstanding(customer, company, ignore_outstanding_sales_order) if extra_amount > 0: customer_outstanding += flt(extra_amount) credit_limit = get_credit_limit(customer, company) if credit_limit > 0 and flt(customer_outstanding) > credit_limit: msgprint(_("Credit limit has been crossed for customer {0} ({1}/{2})") .format(customer, customer_outstanding, credit_limit)) # If not authorized person raise exception credit_controller_role = frappe.db.get_single_value('Accounts Settings', 'credit_controller') if not credit_controller_role or credit_controller_role not in frappe.get_roles(): # form a list of emails for the credit controller users credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager") credit_controller_users = [user[0] for user in credit_controller_users] # form a list of emails and names to show to the user credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})] credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list] if not credit_controller_users: frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer))) message = """Please contact any of the following users to extend the credit limits for {0}: <br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users)) # if the current user does not have permissions to override credit limit, # prompt them to send out an email to the controller users frappe.msgprint(message, title="Notify", raise_exception=1, primary_action={ 'label': 'Send Email', 'server_action': 'erpnext.selling.doctype.customer.customer.send_emails', 'hide_on_success': 1, 'args': { 'customer': customer, 'customer_outstanding': customer_outstanding, 'credit_limit': credit_limit, 'credit_controller_users_list': credit_controller_users_list, 'document_link': frappe.utils.get_link_to_form(reference_doctype, reference_document) } } )
def send_login_mail(self, subject, template, add_args, now=None): """send mail with login details""" from frappe.utils.user import get_user_fullname from frappe.utils import get_url full_name = get_user_fullname(frappe.session['user']) if full_name == "Guest": full_name = "Administrator" args = { 'first_name': self.first_name or self.last_name or "user", 'user': self.name, 'title': subject, 'login_url': get_url(), 'user_fullname': full_name } args.update(add_args) sender = frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None frappe.sendmail(recipients=self.email, sender=sender, subject=subject, template=template, args=args, header=[subject, "green"], delayed=(not now) if now!=None else self.flags.delay_emails, retry=3)
def get_starrers(self): """Return list of users who have starred this document.""" return [(get_formatted_email(user) or user) for user in self.get_parent_doc().get_starred_by()]
def _make( doctype=None, name=None, content=None, subject=None, sent_or_received="Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments="[]", send_me_a_copy=False, cc=None, bcc=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None, add_signature=True, ): """Internal method to make a new communication that ignores Permission checks.""" sender = sender or get_formatted_email(frappe.session.user) recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients cc = list_to_str(cc) if isinstance(cc, list) else cc bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc comm = frappe.get_doc({ "doctype": "Communication", "subject": subject, "content": content, "sender": sender, "sender_full_name": sender_full_name, "recipients": recipients, "cc": cc or None, "bcc": bcc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name, "email_template": email_template, "message_id": get_string_between("<", get_message_id(), ">"), "read_receipt": read_receipt, "has_attachment": 1 if attachments else 0, "communication_type": communication_type, }) comm.flags.skip_add_signature = not add_signature comm.insert(ignore_permissions=True) if isinstance(attachments, string_types): attachments = json.loads(attachments) # if not committed, delayed task doesn't find the communication if attachments: add_attachments(comm.name, attachments) if cint(send_email): # Raise error if outgoing email account is missing _ = frappe.email.smtp.get_outgoing_email_account( append_to=comm.doctype, sender=comm.sender) frappe.flags.print_letterhead = cint(print_letterhead) comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) return { "name": comm.name, "emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None, }
def get_owner_email(self): owner = self.get_parent_doc().owner return get_formatted_email(owner) or owner
def get_starrers(self): """Return list of users who have starred this document.""" return [( get_formatted_email(user) or user ) for user in self.get_parent_doc().get_starred_by()]
def get_owner_email(doc): owner = doc.get_parent_doc().owner return get_formatted_email(owner) or owner
def make(doctype=None, name=None, content=None, subject=None, sent_or_received="Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None, flags=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None, ignore_permissions=False): """Make a new communication. :param doctype: Reference DocType. :param name: Reference Document name. :param content: Communication body. :param subject: Communication subject. :param sent_or_received: Sent or Received (default **Sent**). :param sender: Communcation sender (default current user). :param recipients: Communication recipients as list. :param communication_medium: Medium of communication (default **Email**). :param send_email: Send via email (default **False**). :param print_html: HTML Print format to be sent as attachment. :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string. :param send_me_a_copy: Send a copy to the sender (default **False**). :param email_template: Template which is used to compose mail . """ is_error_report = (doctype == "User" and name == frappe.session.user and subject == "Error Report") send_me_a_copy = cint(send_me_a_copy) if not ignore_permissions: if doctype and name and not is_error_report and not frappe.has_permission( doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'): raise frappe.PermissionError( "You are not allowed to send emails related to: {doctype} {name}" .format(doctype=doctype, name=name)) if not sender: sender = get_formatted_email(frappe.session.user) recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients cc = list_to_str(cc) if isinstance(cc, list) else cc bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc comm = frappe.get_doc({ "doctype": "Communication", "subject": subject, "content": content, "sender": sender, "sender_full_name": sender_full_name, "recipients": recipients, "cc": cc or None, "bcc": bcc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name, "email_template": email_template, "message_id": get_message_id().strip(" <>"), "read_receipt": read_receipt, "has_attachment": 1 if attachments else 0, "communication_type": communication_type }).insert(ignore_permissions=True) comm.save(ignore_permissions=True) if isinstance(attachments, str): attachments = json.loads(attachments) # if not committed, delayed task doesn't find the communication if attachments: add_attachments(comm.name, attachments) if cint(send_email): if not comm.get_outgoing_email_account(): frappe.throw(msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError) comm.send_email(print_html=print_html, print_format=print_format, send_me_a_copy=send_me_a_copy, print_letterhead=print_letterhead) emails_not_sent_to = comm.exclude_emails_list( include_sender=send_me_a_copy) return { "name": comm.name, "emails_not_sent_to": ", ".join(emails_not_sent_to or []) }
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, flags=None,read_receipt=None): """Make a new communication. :param doctype: Reference DocType. :param name: Reference Document name. :param content: Communication body. :param subject: Communication subject. :param sent_or_received: Sent or Received (default **Sent**). :param sender: Communcation sender (default current user). :param recipients: Communication recipients as list. :param communication_medium: Medium of communication (default **Email**). :param send_mail: Send via email (default **False**). :param print_html: HTML Print format to be sent as attachment. :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string. :param send_me_a_copy: Send a copy to the sender (default **False**). """ is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") send_me_a_copy = cint(send_me_a_copy) if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'): raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( doctype=doctype, name=name)) if not sender: sender = get_formatted_email(frappe.session.user) comm = frappe.get_doc({ "doctype":"Communication", "subject": subject, "content": content, "sender": sender, "sender_full_name":sender_full_name, "recipients": recipients, "cc": cc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name, "message_id":email.utils.make_msgid("{0}".format(frappe.local.site)), "read_receipt":read_receipt, "has_attachment": 1 if attachments else 0 }) comm.insert(ignore_permissions=True) # if not committed, delayed task doesn't find the communication if attachments: add_attachments(comm.name,attachments) frappe.db.commit() if cint(send_email): comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) return { "name": comm.name, "emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None }
def make(doctype=None, name=None, content=None, subject=None, sent_or_received="Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None, flags=None, read_receipt=None, print_letterhead=True): """Make a new communication. :param doctype: Reference DocType. :param name: Reference Document name. :param content: Communication body. :param subject: Communication subject. :param sent_or_received: Sent or Received (default **Sent**). :param sender: Communcation sender (default current user). :param recipients: Communication recipients as list. :param communication_medium: Medium of communication (default **Email**). :param send_email: Send via email (default **False**). :param print_html: HTML Print format to be sent as attachment. :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string. :param send_me_a_copy: Send a copy to the sender (default **False**). """ is_error_report = (doctype == "User" and name == frappe.session.user and subject == "Error Report") send_me_a_copy = cint(send_me_a_copy) if doctype and name and not is_error_report and not frappe.has_permission( doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'): raise frappe.PermissionError( "You are not allowed to send emails related to: {doctype} {name}". format(doctype=doctype, name=name)) if not sender: sender = get_formatted_email(frappe.session.user) if isinstance(recipients, list): recipients = ', '.join(recipients) comm = frappe.get_doc({ "doctype": "Communication", "subject": subject, "content": content, "sender": sender, "sender_full_name": sender_full_name, "recipients": recipients, "cc": cc or None, "bcc": bcc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name, "message_id": get_message_id().strip(" <>"), "read_receipt": read_receipt, "has_attachment": 1 if attachments else 0 }) comm.insert(ignore_permissions=True) if not doctype: # if no reference given, then send it against the communication comm.db_set( dict(reference_doctype='Communication', reference_name=comm.name)) if isinstance(attachments, string_types): attachments = json.loads(attachments) # if not committed, delayed task doesn't find the communication if attachments: add_attachments(comm.name, attachments) frappe.db.commit() if cint(send_email): frappe.flags.print_letterhead = cint(print_letterhead) comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) return { "name": comm.name, "emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None }
def get_owner_email(doc): owner = get_parent_doc(doc).owner return get_formatted_email(owner) or owner
def get_mail_sender_with_displayname(self): return get_formatted_email(self.mail_sender_fullname(), mail=self.mail_sender())