Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SENDGRID_API_KEY=dump_api_key
SENDGRID_FROM_EMAIL=dump_from_email
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ docker compose build
docker compose up
```

## Env vars
Copy `.env.example` to `.env` and adjust as needed.

Notes:
- The docker-compose.yml mounts the project into /app and runs `pytest -q` by default.
- Stop the stack with Ctrl+C (in the same terminal) or by running `docker compose down` in another terminal.
67 changes: 67 additions & 0 deletions mailing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Mailing module — using mailing.mailing.Email

This module wraps SendGrid to send emails from your Python project.

Prerequisites
- Install dependencies (SendGrid is already listed in requirements):
pip install -r requirements/base.txt
- Set required environment variables (used by settings.py):
- SENDGRID_API_KEY: Your SendGrid API key (optional if you pass api_key to Email).
- SENDGRID_FROM_EMAIL: Default sender email (must be verified in SendGrid).

Basic usage
1) Import and construct an Email instance
```python
from mailing.mailing import Email

email = Email(
subject="Hello from python-mailing",
message="This is the plain-text fallback body.",
# recipient_list=["recipient@example.com"],
# Optional overrides (each parameter is optional):
# from_email="no-reply@yourdomain.com",
# api_key="SG.xxxxxx.yyyyyy", # overrides settings.SENDGRID_API_KEY for this Email
html_content="<p>This is the <strong>HTML</strong> body.</p>",
)
# Send with SendGrid
response = email.send_sendgrid_email()
if response is not None:
print("Sent! Status:", response.status_code)
else:
print("Sending failed — see logs for details.")
```

Notes and tips
- recipient_list can include multiple addresses:
Email(subject="...", message="...", recipient_list=["a@x.com", "b@y.com"]).
- If you don’t need HTML, omit html_content and only plain text will be sent.
- You can override the default sender by passing from_email. If not provided, settings.FROM_EMAIL is used.
- You can override the SendGrid API key per email by passing api_key to Email(...). If not provided, settings.SENDGRID_API_KEY is used.
- On exceptions, send_sendgrid_email returns None and logs the error (including SendGrid error body when available).
- Make sure your SENDGRID_FROM_EMAIL and any sender domain are verified in SendGrid to avoid 403/unauthorized or 400 errors.
- Ensure SENDGRID_API_KEY has Mail Send permissions.

Example with multiple recipients and custom from_email
```python
email = Email(
subject="Weekly report",
message="See the attached summary.",
recipient_list=["team1@example.com", "team2@example.com"],
from_email="reports@yourdomain.com",
)
email.send_sendgrid_email()
```


Example overriding SendGrid API key per Email instance
```python
from mailing.mailing import Email

email = Email(
subject="On-demand API key",
message="Body",
recipient_list=["user@example.com"],
api_key="SG.xxxxxx.yyyyyy", # this value will be used instead of settings.SENDGRID_API_KEY
)
email.send_sendgrid_email()
```
Empty file added mailing/__init__.py
Empty file.
49 changes: 49 additions & 0 deletions mailing/mailing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Send emails."""

import logging
from typing import Any

from sendgrid import SendGridAPIClient, Mail

import settings

logger = logging.getLogger(__name__)


class Email:
def __init__(
self,
subject: str,
message: str,
recipient_list: list[str],
from_email: str = None,
html_content: str = None,
api_key: str = None,
**kwargs: Any,
):
"""Initialize the Email class."""
self.subject = subject
self.message = message
self.from_email = from_email or settings.SENDGRID_FROM_EMAIL
self.recipient_list = recipient_list
self.html_content = html_content
self.api_key = api_key

def send_sendgrid_email(self, **kwargs: Any) -> Any | None:
"""Send an email using SendGrid."""
sg = SendGridAPIClient(self.api_key or settings.SENDGRID_API_KEY)
message = Mail(
from_email=self.from_email,
to_emails=self.recipient_list,
subject=self.subject,
html_content=self.html_content or None,
plain_text_content=self.message,
)
try:
response = sg.send(message)
return response
except Exception as e:
if hasattr(e, "body"):
logger.error("SendGrid error body: %s", e.body)
logger.exception("SendGrid exception")
return None
3 changes: 2 additions & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
black==25.9.0
pytest==8.4.2
pytest-mock==3.15.1
pytest-mock==3.15.1
sendgrid==6.12.5
3 changes: 2 additions & 1 deletion requirements/local.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-r base.txt

pre-commit==4.3.0
setuptools==80.9.0
setuptools==80.9.0
ipython==9.6.0
4 changes: 4 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import os

SENDGRID_API_KEY = os.environ.get("SENDGRID_API_KEY")
SENDGRID_FROM_EMAIL = os.environ.get("SENDGRID_FROM_EMAIL")