140 lines
5.7 KiB
Python
140 lines
5.7 KiB
Python
"""
|
|
Main public API.
|
|
"""
|
|
import email.message
|
|
import socket
|
|
import ssl
|
|
from typing import Dict, Optional, Sequence, Tuple, Union, cast
|
|
|
|
from .response import SMTPResponse
|
|
from .smtp import DEFAULT_TIMEOUT, SMTP
|
|
from .typing import SocketPathType
|
|
|
|
|
|
__all__ = ("send",)
|
|
|
|
|
|
async def send(
|
|
message: Union[email.message.EmailMessage, email.message.Message, str, bytes],
|
|
/,
|
|
*,
|
|
sender: Optional[str] = None,
|
|
recipients: Optional[Union[str, Sequence[str]]] = None,
|
|
mail_options: Optional[Sequence[str]] = None,
|
|
rcpt_options: Optional[Sequence[str]] = None,
|
|
hostname: Optional[str] = "localhost",
|
|
port: Optional[int] = None,
|
|
username: Optional[Union[str, bytes]] = None,
|
|
password: Optional[Union[str, bytes]] = None,
|
|
local_hostname: Optional[str] = None,
|
|
source_address: Optional[Tuple[str, int]] = None,
|
|
timeout: Optional[float] = DEFAULT_TIMEOUT,
|
|
use_tls: bool = False,
|
|
start_tls: Optional[bool] = None,
|
|
validate_certs: bool = True,
|
|
client_cert: Optional[str] = None,
|
|
client_key: Optional[str] = None,
|
|
tls_context: Optional[ssl.SSLContext] = None,
|
|
cert_bundle: Optional[str] = None,
|
|
socket_path: Optional[SocketPathType] = None,
|
|
sock: Optional[socket.socket] = None,
|
|
) -> Tuple[Dict[str, SMTPResponse], str]:
|
|
"""
|
|
Send an email message. On await, connects to the SMTP server using the details
|
|
provided, sends the message, then disconnects.
|
|
|
|
:param message: Message text. Either an :py:class:`email.message.EmailMessage`
|
|
object, ``str`` or ``bytes``. If an :py:class:`email.message.EmailMessage`
|
|
object is provided, sender and recipients set in the message headers will be
|
|
used, unless overridden by the respective keyword arguments.
|
|
:keyword sender: From email address. Not required if an
|
|
:py:class:`email.message.EmailMessage` object is provided for the `message`
|
|
argument.
|
|
:keyword recipients: Recipient email addresses. Not required if an
|
|
:py:class:`email.message.EmailMessage` object is provided for the `message`
|
|
argument.
|
|
:keyword hostname: Server name (or IP) to connect to. Defaults to "localhost".
|
|
:keyword port: Server port. Defaults ``465`` if ``use_tls`` is ``True``,
|
|
``587`` if ``start_tls`` is ``True``, or ``25`` otherwise.
|
|
:keyword username: Username to login as after connect.
|
|
:keyword password: Password for login after connect.
|
|
:keyword local_hostname: The hostname of the client. If specified, used as the
|
|
FQDN of the local host in the HELO/EHLO command. Otherwise, the result of
|
|
:func:`socket.getfqdn`. **Note that getfqdn will block the event loop.**
|
|
:keyword source_address: Takes a 2-tuple (host, port) for the socket to bind to
|
|
as its source address before connecting. If the host is '' and port is 0,
|
|
the OS default behavior will be used.
|
|
:keyword timeout: Default timeout value for the connection, in seconds.
|
|
Defaults to 60.
|
|
:keyword use_tls: If True, make the initial connection to the server
|
|
over TLS/SSL. Mutually exclusive with ``start_tls``; if the server uses
|
|
STARTTLS, ``use_tls`` should be ``False``.
|
|
:keyword start_tls: Flag to initiate a STARTTLS upgrade on connect.
|
|
If ``None`` (the default), upgrade will be initiated if supported by the
|
|
server.
|
|
If ``True``, and upgrade will be initiated regardless of server support.
|
|
If ``False``, no upgrade will occur.
|
|
Mutually exclusive with ``use_tls``.
|
|
:keyword validate_certs: Determines if server certificates are
|
|
validated. Defaults to ``True``.
|
|
:keyword client_cert: Path to client side certificate, for TLS.
|
|
:keyword client_key: Path to client side key, for TLS.
|
|
:keyword tls_context: An existing :py:class:`ssl.SSLContext`, for TLS.
|
|
Mutually exclusive with ``client_cert``/``client_key``.
|
|
:keyword cert_bundle: Path to certificate bundle, for TLS verification.
|
|
:keyword socket_path: Path to a Unix domain socket. Not compatible with
|
|
hostname or port. Accepts str, bytes, or a pathlike object.
|
|
:keyword sock: An existing, connected socket object. If given, none of
|
|
hostname, port, or socket_path should be provided.
|
|
|
|
:raises ValueError: required arguments missing or mutually exclusive options
|
|
provided
|
|
"""
|
|
if not isinstance(message, (email.message.EmailMessage, email.message.Message)):
|
|
if not recipients:
|
|
raise ValueError("Recipients must be provided with raw messages.")
|
|
if not sender:
|
|
raise ValueError("Sender must be provided with raw messages.")
|
|
|
|
sender = cast(str, sender)
|
|
recipients = cast(Union[str, Sequence[str]], recipients)
|
|
|
|
client = SMTP(
|
|
hostname=hostname,
|
|
port=port,
|
|
local_hostname=local_hostname,
|
|
source_address=source_address,
|
|
timeout=timeout,
|
|
use_tls=use_tls,
|
|
start_tls=start_tls,
|
|
validate_certs=validate_certs,
|
|
client_cert=client_cert,
|
|
client_key=client_key,
|
|
tls_context=tls_context,
|
|
cert_bundle=cert_bundle,
|
|
socket_path=socket_path,
|
|
sock=sock,
|
|
username=username,
|
|
password=password,
|
|
)
|
|
|
|
async with client:
|
|
if isinstance(message, (email.message.EmailMessage, email.message.Message)):
|
|
result = await client.send_message(
|
|
message,
|
|
sender=sender,
|
|
recipients=recipients,
|
|
mail_options=mail_options,
|
|
rcpt_options=rcpt_options,
|
|
)
|
|
else:
|
|
result = await client.sendmail(
|
|
sender,
|
|
recipients,
|
|
message,
|
|
mail_options=mail_options,
|
|
rcpt_options=rcpt_options,
|
|
)
|
|
|
|
return result
|