View a markdown version of this page

使用 SDK for Python (Boto3) 的 Amazon SES API v2 範例 - AWS SDK 程式碼範例

文件 AWS 開發套件範例 GitHub 儲存庫中有更多可用的 AWS SDK 範例

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

使用 SDK for Python (Boto3) 的 Amazon SES API v2 範例

下列程式碼範例示範如何使用 適用於 Python (Boto3) 的 AWS SDK 搭配 Amazon SES API v2 執行動作和實作常見案例。

Actions 是大型程式的程式碼摘錄,必須在內容中執行。雖然動作會告訴您如何呼叫個別服務函數,但您可以在其相關情境中查看內容中的動作。

案例是向您展示如何呼叫服務中的多個函數或與其他 AWS 服務組合來完成特定任務的程式碼範例。

每個範例均包含完整原始碼的連結,您可在連結中找到如何設定和執行內容中程式碼的相關指示。

開始使用

下列程式碼範例說明如何開始使用 Amazon SES API v2。

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def hello_sesv2(sesv2_client): """ Use the AWS SDK for Python (Boto3) to create an Amazon SESv2 client and list the email identities in your account. This example uses the default settings specified in your shared credentials and config files. :param sesv2_client: A Boto3 SESv2 client object. """ print("Hello, Amazon SESv2. Let's list up to 5 email identities:\n") try: response = sesv2_client.list_email_identities(PageSize=5) identities = response["EmailIdentities"] if not identities: print( "No email identities found. " "Use CreateEmailIdentity to add one." ) else: for identity in identities: print( f" Identity: {identity['IdentityName']}" f" Type: {identity['IdentityType']}" f" Status: {identity['VerificationStatus']}" f" Sending: {'Enabled' if identity['SendingEnabled'] else 'Disabled'}" ) print(f"\nShowing {len(identities)} email identity(ies).") except ClientError as err: logger.error( "Couldn't list email identities. Here's why: %s: %s", err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise
  • 如需 API 詳細資訊,請參閱《適用於 AWS Python (Boto3) 的 SDK API 參考》中的 ListEmailIdentities

動作

以下程式碼範例顯示如何使用 CreateContact

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: # Create a new contact self.ses_client.create_contact( ContactListName=CONTACT_LIST_NAME, EmailAddress=email ) print(f"Contact with email '{email}' created successfully.") # Send the welcome email self.ses_client.send_email( FromEmailAddress=self.verified_email, Destination={"ToAddresses": [email]}, Content={ "Simple": { "Subject": { "Data": "Welcome to the Weekly Coupons Newsletter" }, "Body": { "Text": {"Data": welcome_text}, "Html": {"Data": welcome_html}, }, } }, ) print(f"Welcome email sent to '{email}'.") if self.sleep: # 1 email per second in sandbox mode, remove in production. sleep(1.1) except ClientError as e: # If the contact already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Contact with email '{email}' already exists. Skipping...") else: raise e
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 CreateContact

以下程式碼範例顯示如何使用 CreateContactList

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: self.ses_client.create_contact_list(ContactListName=CONTACT_LIST_NAME) print(f"Contact list '{CONTACT_LIST_NAME}' created successfully.") except ClientError as e: # If the contact list already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Contact list '{CONTACT_LIST_NAME}' already exists.") else: raise e
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 CreateContactList

以下程式碼範例顯示如何使用 CreateEmailIdentity

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: self.ses_client.create_email_identity(EmailIdentity=self.verified_email) print(f"Email identity '{self.verified_email}' created successfully.") except ClientError as e: # If the email identity already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Email identity '{self.verified_email}' already exists.") else: raise e
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 CreateEmailIdentity

以下程式碼範例顯示如何使用 CreateEmailTemplate

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: template_content = { "Subject": "Weekly Coupons Newsletter", "Html": load_file_content("coupon-newsletter.html"), "Text": load_file_content("coupon-newsletter.txt"), } self.ses_client.create_email_template( TemplateName=TEMPLATE_NAME, TemplateContent=template_content ) print(f"Email template '{TEMPLATE_NAME}' created successfully.") except ClientError as e: # If the template already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Email template '{TEMPLATE_NAME}' already exists.") else: raise e
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 CreateEmailTemplate

以下程式碼範例顯示如何使用 DeleteContactList

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: self.ses_client.delete_contact_list(ContactListName=CONTACT_LIST_NAME) print(f"Contact list '{CONTACT_LIST_NAME}' deleted successfully.") except ClientError as e: # If the contact list doesn't exist, skip and proceed if e.response["Error"]["Code"] == "NotFoundException": print(f"Contact list '{CONTACT_LIST_NAME}' does not exist.") else: print(e)
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 DeleteContactList

以下程式碼範例顯示如何使用 DeleteEmailIdentity

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: self.ses_client.delete_email_identity(EmailIdentity=self.verified_email) print(f"Email identity '{self.verified_email}' deleted successfully.") except ClientError as e: # If the email identity doesn't exist, skip and proceed if e.response["Error"]["Code"] == "NotFoundException": print(f"Email identity '{self.verified_email}' does not exist.") else: print(e)
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 DeleteEmailIdentity

以下程式碼範例顯示如何使用 DeleteEmailTemplate

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: self.ses_client.delete_email_template(TemplateName=TEMPLATE_NAME) print(f"Email template '{TEMPLATE_NAME}' deleted successfully.") except ClientError as e: # If the email template doesn't exist, skip and proceed if e.response["Error"]["Code"] == "NotFoundException": print(f"Email template '{TEMPLATE_NAME}' does not exist.") else: print(e)
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 DeleteEmailTemplate

以下程式碼範例顯示如何使用 GetEmailIdentity

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

class SESv2Wrapper: """Encapsulates Amazon SESv2 email sending actions.""" def __init__(self, sesv2_client: Any) -> None: """ Initializes the SESv2Wrapper with an SESv2 client. :param sesv2_client: A Boto3 SESv2 client. """ self.sesv2_client = sesv2_client @classmethod def from_client(cls) -> "SESv2Wrapper": """ Creates an SESv2Wrapper instance with a default Boto3 SESv2 client. :return: A new SESv2Wrapper instance. """ sesv2_client = boto3.client("sesv2") return cls(sesv2_client) def get_email_identity(self, email_address: str) -> Dict[str, Any]: """ Gets information about an email identity, including its verification status. :param email_address: The email address or domain to look up. :return: A dictionary with identity information including verification status. :raises ClientError: If the identity is not found (NotFoundException). """ try: response = self.sesv2_client.get_email_identity( EmailIdentity=email_address ) logger.info("Got email identity for %s.", email_address) return response except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": logger.info( "Email identity %s not found.", email_address ) else: logger.error( "Couldn't get email identity %s. Here's why: %s: %s", email_address, err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise
  • 如需 API 詳細資訊,請參閱《適用於 AWS Python (Boto3) 的 SDK API 參考》中的 GetEmailIdentity

以下程式碼範例顯示如何使用 ListContacts

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: contacts_response = self.ses_client.list_contacts( ContactListName=CONTACT_LIST_NAME ) except ClientError as e: if e.response["Error"]["Code"] == "NotFoundException": print(f"Contact list '{CONTACT_LIST_NAME}' does not exist.") return else: raise e
  • 如需 API 詳細資訊,請參閱《AWS SDK for Python (Boto3) API 參考》中的 ListContacts

以下程式碼範例顯示如何使用 SendBulkEmail

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

class SESv2Wrapper: """Encapsulates Amazon SESv2 email sending actions.""" def __init__(self, sesv2_client: Any) -> None: """ Initializes the SESv2Wrapper with an SESv2 client. :param sesv2_client: A Boto3 SESv2 client. """ self.sesv2_client = sesv2_client @classmethod def from_client(cls) -> "SESv2Wrapper": """ Creates an SESv2Wrapper instance with a default Boto3 SESv2 client. :return: A new SESv2Wrapper instance. """ sesv2_client = boto3.client("sesv2") return cls(sesv2_client) def send_bulk_email( self, from_address: str, template_name: str, default_template_data: str, bulk_entries: List[Dict[str, Any]], attachments: Optional[List[Dict[str, Any]]] = None, ) -> List[Dict[str, Any]]: """ Sends a templated email to multiple recipients in a single API call. All recipients receive the same attachment(s) defined in the default content, while template data can be personalized per recipient. :param from_address: The verified sender email address. :param template_name: The name of an existing email template. :param default_template_data: Default JSON template data string. :param bulk_entries: A list of BulkEmailEntry dicts, each containing 'Destination' and optionally 'ReplacementEmailContent'. :param attachments: An optional list of attachment dicts for all recipients. :return: A list of BulkEmailEntryResult dicts with status and MessageId. :raises ClientError: If the message is rejected (MessageRejected). """ try: template_content: Dict[str, Any] = { "TemplateName": template_name, "TemplateData": default_template_data, } if attachments: template_content["Attachments"] = attachments response = self.sesv2_client.send_bulk_email( FromEmailAddress=from_address, DefaultContent={"Template": template_content}, BulkEmailEntries=bulk_entries, ) results = response.get("BulkEmailEntryResults", []) logger.info( "Sent bulk email from %s to %d recipients.", from_address, len(bulk_entries), ) return results except ClientError as err: if err.response["Error"]["Code"] == "MessageRejected": logger.error( "Bulk message was rejected. Check that the template " "exists, attachment file types are supported, and " "total message size is within limits. Details: %s", err.response["Error"]["Message"], ) else: logger.error( "Couldn't send bulk email. Here's why: %s: %s", err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise
  • 如需 API 詳細資訊,請參閱《適用於 AWS Python (Boto3) 的 SDK API 參考》中的 SendBulkEmail

以下程式碼範例顯示如何使用 SendEmail

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

向聯絡人列表中的所有成員傳送消息。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep self.ses_client.send_email( FromEmailAddress=self.verified_email, Destination={"ToAddresses": [email]}, Content={ "Simple": { "Subject": { "Data": "Welcome to the Weekly Coupons Newsletter" }, "Body": { "Text": {"Data": welcome_text}, "Html": {"Data": welcome_html}, }, } }, ) print(f"Welcome email sent to '{email}'.")

使用範本來傳送訊息給聯絡人清單中的所有人員。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep self.ses_client.send_email( FromEmailAddress=self.verified_email, Destination={"ToAddresses": [email_address]}, Content={ "Template": { "TemplateName": TEMPLATE_NAME, "TemplateData": coupon_items, } }, ListManagementOptions={"ContactListName": CONTACT_LIST_NAME}, )
  • 如需 API 詳細資訊,請參閱《適用於 Python (Boto3) 的AWS SDK API 參考》中的 SendEmail

案例

下列程式碼範例示範如何使用 Amazon SES API v2 傳送具有附件的電子郵件。

  • 建立大量傳送的電子郵件範本。

  • 傳送包含檔案附件的簡單電子郵件。

  • 使用內嵌映像傳送簡單的電子郵件。

  • 使用附件傳送大量範本電子郵件。

  • 清除資源。

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

執行示範電子郵件附件的互動式案例。

class SESv2EmailAttachmentsScenario: """ Demonstrates sending emails with attachments using Amazon SESv2. This scenario demonstrates: 1. Setting up an email identity and template. 2. Sending a simple email with a file attachment. 3. Sending a simple email with an inline image. 4. Sending bulk templated emails with attachments. 5. Cleaning up created resources. """ TEMPLATE_NAME = "AttachmentDemoTemplate" def __init__(self, sesv2_wrapper: SESv2Wrapper) -> None: """ :param sesv2_wrapper: An instance of the SESv2Wrapper class. """ self.sesv2_wrapper = sesv2_wrapper self.sender_email = "" self.recipient_emails: list = [] self.identity_was_created = False def run_scenario(self) -> None: """Runs the SESv2 email attachments scenario.""" print("-" * 88) print("Welcome to the Amazon SESv2 Email Attachments Scenario.") print("-" * 88) print( "This scenario demonstrates how to send emails with attachments\n" "using SESv2 attachment support. SES handles MIME\n" "construction automatically, so you don't need to build raw\n" "MIME messages.\n" ) try: self._setup() self._step1_send_email_with_attachment() self._step2_send_email_with_inline_image() self._step3_send_bulk_email_with_attachments() except Exception as e: logger.error("Scenario failed: %s", e) print(f"\nThe scenario encountered an error: {e}") finally: self._cleanup() # ---------- Setup ---------- def _setup(self) -> None: """ Prompts for configuration, verifies the sender identity, prepares a sample attachment, and creates an email template. """ print("\n--- Setup ---\n") # Prompt for sender and recipient addresses. print( "Both sender and recipient addresses must be verified if your\n" "account is in the SES sandbox.\n" ) self.sender_email = q.ask( "Enter a verified sender email address: " ) recipient_input = q.ask( "Enter one or more recipient email addresses (comma-separated): " ) self.recipient_emails = [ addr.strip() for addr in recipient_input.split(",") if addr.strip() ] # Verify the sender identity. print(f"\nChecking identity for {self.sender_email}...") try: identity_info = self.sesv2_wrapper.get_email_identity( self.sender_email ) verified = identity_info.get("VerifiedForSendingStatus", False) if verified: print(f" {self.sender_email} is verified and ready to send.") else: print( f" {self.sender_email} exists but is not yet verified." ) except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": print( f" Identity {self.sender_email} not found. " "Creating it now..." ) result = self.sesv2_wrapper.create_email_identity( self.sender_email ) self.identity_was_created = True print( f" Identity created. Verification status: " f"{result.get('VerifiedForSendingStatus', False)}" ) print( " Check your inbox and click the verification link " "before continuing." ) q.ask("Press Enter when you have verified the address...") else: raise # Create the email template for the bulk-send step. print("\nCreating email template for the bulk email step...") try: self.sesv2_wrapper.create_email_template( template_name=self.TEMPLATE_NAME, subject="Bulk Email with Attachment for {{name}}", html_body=( "<h1>Hello {{name}}</h1>" "<p>Please find the attached document.</p>" ), text_body=( "Hello {{name}}, Please find the attached document." ), ) print(f" Template '{self.TEMPLATE_NAME}' created.\n") except ClientError as err: if err.response["Error"]["Code"] == "AlreadyExistsException": print( f" Template '{self.TEMPLATE_NAME}' already exists. " "Using it.\n" ) else: raise # ---------- Step 1: Simple email with file attachment ---------- def _step1_send_email_with_attachment(self) -> None: """Sends a simple email with a text file attachment.""" print("\n--- Step 1: Send a Simple Email with a File Attachment ---\n") print( "Creating a sample text file attachment and sending it with\n" "the Simple email content type. SES constructs the MIME message\n" "automatically.\n" ) # Prepare a sample text file as bytes. sample_content = b"This is a sample report attachment." attachment = { "RawContent": sample_content, "FileName": "sample-report.txt", "ContentType": "text/plain", "ContentDisposition": "ATTACHMENT", "ContentDescription": "Sample report text file", "ContentTransferEncoding": "BASE64", } print( "Note: When using an AWS SDK, the SDK handles base64 encoding\n" "automatically. Direct API callers must encode content themselves.\n" ) message_id = self.sesv2_wrapper.send_email( from_address=self.sender_email, to_addresses=self.recipient_emails, subject="SESv2 Attachment Demo - Simple Email with Attachment", html_body=( "<h1>Attachment Demo</h1>" "<p>Please see the attached <b>report document</b>.</p>" ), text_body="Please see the attached report document.", attachments=[attachment], ) print(f" Email sent. MessageId: {message_id}") print( " SES automatically constructed the MIME message with the " "attachment.\n" ) # ---------- Step 2: Simple email with inline image ---------- def _step2_send_email_with_inline_image(self) -> None: """Sends a simple email with an inline image that renders in HTML.""" print("\n--- Step 2: Send a Simple Email with an Inline Image ---\n") print( "This step demonstrates INLINE disposition. The image renders\n" "directly in the HTML body using a 'cid:' reference instead of\n" "appearing as a downloadable attachment.\n" ) # Create a minimal 1x1 red PNG (valid PNG file). sample_image = ( b"\x89PNG\r\n\x1a\n" # PNG signature b"\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01" b"\x08\x02\x00\x00\x00\x90wS\xde" # 1x1 RGB b"\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01" b"\x00\x05\x18\xd8N" # compressed data b"\x00\x00\x00\x00IEND\xaeB`\x82" # IEND ) attachment = { "RawContent": sample_image, "FileName": "logo.png", "ContentType": "image/png", "ContentDisposition": "INLINE", "ContentId": "logo123", "ContentDescription": "Company logo", "ContentTransferEncoding": "BASE64", } html_body = ( "<html><body>" "<h1>Inline Image Demo</h1>" "<p>Here is our logo:</p>" '<img src="cid:logo123" alt="Company Logo">' "</body></html>" ) message_id = self.sesv2_wrapper.send_email( from_address=self.sender_email, to_addresses=self.recipient_emails, subject="SESv2 Attachment Demo - Inline Image", html_body=html_body, text_body=( "Inline Image Demo - Please view this email in an " "HTML-capable client to see the embedded image." ), attachments=[attachment], ) print(f" Email sent. MessageId: {message_id}") print( " The ContentId 'logo123' is referenced in the HTML body via\n" " 'cid:logo123', which lets the image render inline.\n" ) # ---------- Step 3: Bulk templated email with attachments ---------- def _step3_send_bulk_email_with_attachments(self) -> None: """Sends bulk templated emails with attachments to multiple recipients.""" print("\n--- Step 3: Send Bulk Templated Emails with Attachments ---\n") print( "Using SendBulkEmail to send a templated email with an attachment\n" "to multiple recipients in a single API call. Each recipient gets\n" "personalized content via template data.\n" ) sample_content = b"This is a sample report attachment." attachment = { "RawContent": sample_content, "FileName": "sample-report.txt", "ContentType": "text/plain", "ContentDisposition": "ATTACHMENT", "ContentDescription": "Sample report for bulk recipients", "ContentTransferEncoding": "BASE64", } # Build one entry per recipient with personalized names. names = ["Alice", "Bob", "Charlie", "Diana", "Eve"] bulk_entries = [] for i, email in enumerate(self.recipient_emails): name = names[i] if i < len(names) else f"Recipient{i + 1}" bulk_entries.append( { "Destination": {"ToAddresses": [email]}, "ReplacementEmailContent": { "ReplacementTemplate": { "ReplacementTemplateData": json.dumps( {"name": name} ) } }, } ) results = self.sesv2_wrapper.send_bulk_email( from_address=self.sender_email, template_name=self.TEMPLATE_NAME, default_template_data='{"name": "Valued Customer"}', bulk_entries=bulk_entries, attachments=[attachment], ) print(" Bulk email results:") for idx, result in enumerate(results): status = result.get("Status", "Unknown") msg_id = result.get("MessageId", "N/A") error = result.get("Error", "") recipient = ( self.recipient_emails[idx] if idx < len(self.recipient_emails) else "Unknown" ) print(f" {recipient}: Status={status}, MessageId={msg_id}") if error: print(f" Error: {error}") print( "\n All recipients receive the same attachment(s) defined in\n" " DefaultContent. Template data is personalized per recipient.\n" ) # ---------- Cleanup ---------- def _cleanup(self) -> None: """Deletes the email template and optionally the email identity.""" print("\n--- Cleanup ---\n") # Delete the email template. try: self.sesv2_wrapper.delete_email_template(self.TEMPLATE_NAME) print(f" Template '{self.TEMPLATE_NAME}' deleted.") except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": print( f" Template '{self.TEMPLATE_NAME}' was already deleted." ) else: logger.error("Failed to delete template: %s", err) # Optionally delete the email identity. if self.identity_was_created and self.sender_email: delete_identity = q.ask( f"Delete the email identity '{self.sender_email}'? (y/n) ", q.is_yesno, ) if delete_identity: try: self.sesv2_wrapper.delete_email_identity( self.sender_email ) print( f" Email identity '{self.sender_email}' deleted." ) except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": print( f" Identity '{self.sender_email}' was " "already deleted." ) else: logger.error( "Failed to delete identity: %s", err ) else: print( f" Skipping identity deletion for {self.sender_email}." ) else: print( " Sender identity was pre-existing. Skipping deletion." ) print("\nAll resources have been cleaned up.") print("-" * 88)

建立 SESv2 包裝函式類別來管理操作。

class SESv2Wrapper: """Encapsulates Amazon SESv2 email sending actions.""" def __init__(self, sesv2_client: Any) -> None: """ Initializes the SESv2Wrapper with an SESv2 client. :param sesv2_client: A Boto3 SESv2 client. """ self.sesv2_client = sesv2_client @classmethod def from_client(cls) -> "SESv2Wrapper": """ Creates an SESv2Wrapper instance with a default Boto3 SESv2 client. :return: A new SESv2Wrapper instance. """ sesv2_client = boto3.client("sesv2") return cls(sesv2_client) def get_email_identity(self, email_address: str) -> Dict[str, Any]: """ Gets information about an email identity, including its verification status. :param email_address: The email address or domain to look up. :return: A dictionary with identity information including verification status. :raises ClientError: If the identity is not found (NotFoundException). """ try: response = self.sesv2_client.get_email_identity( EmailIdentity=email_address ) logger.info("Got email identity for %s.", email_address) return response except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": logger.info( "Email identity %s not found.", email_address ) else: logger.error( "Couldn't get email identity %s. Here's why: %s: %s", email_address, err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise def create_email_identity(self, email_address: str) -> Dict[str, Any]: """ Starts the process of verifying an email identity (email address or domain). :param email_address: The email address or domain to verify. :return: A dictionary with the identity type and verification status. :raises ClientError: If the limit is exceeded (LimitExceededException). """ try: response = self.sesv2_client.create_email_identity( EmailIdentity=email_address ) logger.info( "Started verification for email identity %s.", email_address ) return response except ClientError as err: if err.response["Error"]["Code"] == "LimitExceededException": logger.error( "Couldn't create email identity %s. You have exceeded " "the maximum number of email identities. " "Use an existing verified identity.", email_address, ) else: logger.error( "Couldn't create email identity %s. Here's why: %s: %s", email_address, err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise def create_email_template( self, template_name: str, subject: str, html_body: str, text_body: str, ) -> None: """ Creates an email template for use with templated and bulk email sends. :param template_name: The name for the new template. :param subject: The subject line of the template. May include {{placeholders}}. :param html_body: The HTML body of the template. :param text_body: The plain text body of the template. :raises ClientError: If the template limit is exceeded (LimitExceededException). """ try: self.sesv2_client.create_email_template( TemplateName=template_name, TemplateContent={ "Subject": subject, "Html": html_body, "Text": text_body, }, ) logger.info("Created email template %s.", template_name) except ClientError as err: if err.response["Error"]["Code"] == "LimitExceededException": logger.error( "Couldn't create email template %s. You have exceeded " "the maximum number of email templates. " "Delete unused templates first.", template_name, ) else: logger.error( "Couldn't create email template %s. Here's why: %s: %s", template_name, err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise def send_email( self, from_address: str, to_addresses: List[str], subject: str, html_body: str, text_body: str, attachments: Optional[List[Dict[str, Any]]] = None, ) -> str: """ Sends a simple email message with optional attachments. SES handles MIME construction automatically when using attachments with the Simple content type, so developers don't need to build raw MIME messages. :param from_address: The verified sender email address. :param to_addresses: A list of recipient email addresses. :param subject: The subject line of the email. :param html_body: The HTML body content. :param text_body: The plain text body content. :param attachments: An optional list of attachment dictionaries. Each attachment should contain 'RawContent' (bytes), 'FileName' (str), and optionally 'ContentType', 'ContentDisposition', 'ContentId', 'ContentDescription', and 'ContentTransferEncoding'. :return: The MessageId of the sent email. :raises ClientError: If the message is rejected (MessageRejected). """ try: simple_message: Dict[str, Any] = { "Subject": {"Data": subject}, "Body": { "Html": {"Data": html_body}, "Text": {"Data": text_body}, }, } if attachments: simple_message["Attachments"] = attachments response = self.sesv2_client.send_email( FromEmailAddress=from_address, Destination={"ToAddresses": to_addresses}, Content={"Simple": simple_message}, ) message_id = response["MessageId"] logger.info( "Sent email from %s to %s. MessageId: %s", from_address, to_addresses, message_id, ) return message_id except ClientError as err: if err.response["Error"]["Code"] == "MessageRejected": logger.error( "Message was rejected. Check that attachments use " "supported file types and total message size is " "under 40 MB. Details: %s", err.response["Error"]["Message"], ) else: logger.error( "Couldn't send email. Here's why: %s: %s", err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise def send_bulk_email( self, from_address: str, template_name: str, default_template_data: str, bulk_entries: List[Dict[str, Any]], attachments: Optional[List[Dict[str, Any]]] = None, ) -> List[Dict[str, Any]]: """ Sends a templated email to multiple recipients in a single API call. All recipients receive the same attachment(s) defined in the default content, while template data can be personalized per recipient. :param from_address: The verified sender email address. :param template_name: The name of an existing email template. :param default_template_data: Default JSON template data string. :param bulk_entries: A list of BulkEmailEntry dicts, each containing 'Destination' and optionally 'ReplacementEmailContent'. :param attachments: An optional list of attachment dicts for all recipients. :return: A list of BulkEmailEntryResult dicts with status and MessageId. :raises ClientError: If the message is rejected (MessageRejected). """ try: template_content: Dict[str, Any] = { "TemplateName": template_name, "TemplateData": default_template_data, } if attachments: template_content["Attachments"] = attachments response = self.sesv2_client.send_bulk_email( FromEmailAddress=from_address, DefaultContent={"Template": template_content}, BulkEmailEntries=bulk_entries, ) results = response.get("BulkEmailEntryResults", []) logger.info( "Sent bulk email from %s to %d recipients.", from_address, len(bulk_entries), ) return results except ClientError as err: if err.response["Error"]["Code"] == "MessageRejected": logger.error( "Bulk message was rejected. Check that the template " "exists, attachment file types are supported, and " "total message size is within limits. Details: %s", err.response["Error"]["Message"], ) else: logger.error( "Couldn't send bulk email. Here's why: %s: %s", err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise def delete_email_template(self, template_name: str) -> None: """ Deletes an email template. :param template_name: The name of the template to delete. :raises ClientError: If the template is not found (NotFoundException). """ try: self.sesv2_client.delete_email_template( TemplateName=template_name ) logger.info("Deleted email template %s.", template_name) except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": logger.info( "Email template %s not found or already deleted.", template_name, ) else: logger.error( "Couldn't delete email template %s. Here's why: %s: %s", template_name, err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise def delete_email_identity(self, email_address: str) -> None: """ Deletes an email identity. :param email_address: The email address or domain to delete. :raises ClientError: If the identity is not found (NotFoundException). """ try: self.sesv2_client.delete_email_identity( EmailIdentity=email_address ) logger.info("Deleted email identity %s.", email_address) except ClientError as err: if err.response["Error"]["Code"] == "NotFoundException": logger.info( "Email identity %s not found or already deleted.", email_address, ) else: logger.error( "Couldn't delete email identity %s. Here's why: %s: %s", email_address, err.response["Error"]["Code"], err.response["Error"]["Message"], ) raise

下列程式碼範例示範如何執行 Amazon SES API v2 電子報案例。

適用於 Python 的 SDK (Boto3)
注意

GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫中設定和執行。

def main(): """ The main function that orchestrates the execution of the workflow. """ print(INTRO) ses_client = boto3.client("sesv2") workflow = SESv2Workflow(ses_client) try: workflow.prepare_application() workflow.gather_subscriber_email_addresses() workflow.send_coupon_newsletter() workflow.monitor_and_review() except ClientError as e: print_error(e) workflow.clean_up() class SESv2Workflow: """ A class to manage the SES v2 Coupon Newsletter Workflow. """ def __init__(self, ses_client, sleep=True): self.ses_client = ses_client self.sleep = sleep try: self.ses_client.create_contact_list(ContactListName=CONTACT_LIST_NAME) print(f"Contact list '{CONTACT_LIST_NAME}' created successfully.") except ClientError as e: # If the contact list already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Contact list '{CONTACT_LIST_NAME}' already exists.") else: raise e try: # Create a new contact self.ses_client.create_contact( ContactListName=CONTACT_LIST_NAME, EmailAddress=email ) print(f"Contact with email '{email}' created successfully.") # Send the welcome email self.ses_client.send_email( FromEmailAddress=self.verified_email, Destination={"ToAddresses": [email]}, Content={ "Simple": { "Subject": { "Data": "Welcome to the Weekly Coupons Newsletter" }, "Body": { "Text": {"Data": welcome_text}, "Html": {"Data": welcome_html}, }, } }, ) print(f"Welcome email sent to '{email}'.") if self.sleep: # 1 email per second in sandbox mode, remove in production. sleep(1.1) except ClientError as e: # If the contact already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Contact with email '{email}' already exists. Skipping...") else: raise e try: contacts_response = self.ses_client.list_contacts( ContactListName=CONTACT_LIST_NAME ) except ClientError as e: if e.response["Error"]["Code"] == "NotFoundException": print(f"Contact list '{CONTACT_LIST_NAME}' does not exist.") return else: raise e self.ses_client.send_email( FromEmailAddress=self.verified_email, Destination={"ToAddresses": [email]}, Content={ "Simple": { "Subject": { "Data": "Welcome to the Weekly Coupons Newsletter" }, "Body": { "Text": {"Data": welcome_text}, "Html": {"Data": welcome_html}, }, } }, ) print(f"Welcome email sent to '{email}'.") self.ses_client.send_email( FromEmailAddress=self.verified_email, Destination={"ToAddresses": [email_address]}, Content={ "Template": { "TemplateName": TEMPLATE_NAME, "TemplateData": coupon_items, } }, ListManagementOptions={"ContactListName": CONTACT_LIST_NAME}, ) try: self.ses_client.create_email_identity(EmailIdentity=self.verified_email) print(f"Email identity '{self.verified_email}' created successfully.") except ClientError as e: # If the email identity already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Email identity '{self.verified_email}' already exists.") else: raise e try: template_content = { "Subject": "Weekly Coupons Newsletter", "Html": load_file_content("coupon-newsletter.html"), "Text": load_file_content("coupon-newsletter.txt"), } self.ses_client.create_email_template( TemplateName=TEMPLATE_NAME, TemplateContent=template_content ) print(f"Email template '{TEMPLATE_NAME}' created successfully.") except ClientError as e: # If the template already exists, skip and proceed if e.response["Error"]["Code"] == "AlreadyExistsException": print(f"Email template '{TEMPLATE_NAME}' already exists.") else: raise e try: self.ses_client.delete_contact_list(ContactListName=CONTACT_LIST_NAME) print(f"Contact list '{CONTACT_LIST_NAME}' deleted successfully.") except ClientError as e: # If the contact list doesn't exist, skip and proceed if e.response["Error"]["Code"] == "NotFoundException": print(f"Contact list '{CONTACT_LIST_NAME}' does not exist.") else: print(e) try: self.ses_client.delete_email_identity(EmailIdentity=self.verified_email) print(f"Email identity '{self.verified_email}' deleted successfully.") except ClientError as e: # If the email identity doesn't exist, skip and proceed if e.response["Error"]["Code"] == "NotFoundException": print(f"Email identity '{self.verified_email}' does not exist.") else: print(e) try: self.ses_client.delete_email_template(TemplateName=TEMPLATE_NAME) print(f"Email template '{TEMPLATE_NAME}' deleted successfully.") except ClientError as e: # If the email template doesn't exist, skip and proceed if e.response["Error"]["Code"] == "NotFoundException": print(f"Email template '{TEMPLATE_NAME}' does not exist.") else: print(e)