feat(email): add email category (SES, Azure ACS, raw SMTP)#4
Merged
Conversation
New `cloudrift.email` package with a single `EmailBackend` interface and three native-async backends: - `ses`: AWSSESBackend via aioboto3 SESv2. IAM-role / access-key / profile auth. Switches to `Content.Raw` MIME when attachments or custom headers are present. - `azure_acs`: AzureACSEmailBackend via `azure.communication.email` (sync SDK wrapped with `asyncio.to_thread`). Connection-string, managed-identity, and service-principal auth. - `smtp`: SMTPEmailBackend via `aiosmtplib`. Three modes: plaintext, STARTTLS (587), implicit TLS (465). Covers SendGrid, Mailgun, Postmark, Office365, and MailHog/Mailpit for local dev. `EmailBackend.send()` takes text + HTML bodies, cc/bcc/reply-to, attachments, and custom headers. `send_batch()` ships as a default loop in the ABC; the lazy-`_ensure` / atomic-`close()` pattern from commit 20340f0 carries over to SES and is now covered by `tests/test_ensure_lifecycle.py`. Five new exception types map provider errors at the boundary: EmailError, EmailSendError, RecipientRejectedError, SenderUnverifiedError, EmailThrottledError. 35 unit tests in `tests/test_email.py` (SES via moto, ACS+SMTP via mocks). Full suite: 135 passed.
SahilShetLYZR
approved these changes
May 28, 2026
`patch("azure.identity.ClientSecretCredential", ...)` failed under CI
(Python 3.11, fresh interpreter) because `azure` is a PEP 420 namespace
package — `azure.identity` is not discoverable as an attribute on
`azure` until something imports it directly. Locally the import had
already happened transitively, masking the issue.
Switch to an explicit `import azure.identity` followed by
`patch.object(azure.identity, "ClientSecretCredential", ...)`, which
works on every environment regardless of import order.
CI runs 'uv sync --extra dev' which only pulls the [dev] extra. test_factory_acs_routing_service_principal exercises the AzureACSEmailBackend.from_service_principal factory, whose lazy 'from azure.identity import ClientSecretCredential' fails when the module isn't installed. Other ACS tests dodge this because they use connection-string auth and stub the client directly. The dev extra already ships azure-communication-email for the same 'tests touch it' reason - azure-identity was just missed.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




New
cloudrift.emailpackage with a singleEmailBackendinterface and three native-async backends:ses: AWSSESBackend via aioboto3 SESv2. IAM-role / access-key / profile auth. Switches toContent.RawMIME when attachments or custom headers are present.azure_acs: AzureACSEmailBackend viaazure.communication.email(sync SDK wrapped withasyncio.to_thread). Connection-string, managed-identity, and service-principal auth.smtp: SMTPEmailBackend viaaiosmtplib. Three modes: plaintext, STARTTLS (587), implicit TLS (465). Covers SendGrid, Mailgun, Postmark, Office365, and MailHog/Mailpit for local dev.EmailBackend.send()takes text + HTML bodies, cc/bcc/reply-to, attachments, and custom headers.send_batch()ships as a default loop in the ABC; the lazy-_ensure/ atomic-close()pattern from commit 20340f0 carries over to SES and is now covered bytests/test_ensure_lifecycle.py.Five new exception types map provider errors at the boundary: EmailError, EmailSendError, RecipientRejectedError, SenderUnverifiedError, EmailThrottledError.
35 unit tests in
tests/test_email.py(SES via moto, ACS+SMTP via mocks). Full suite: 135 passed.