Skip to content
Draft
27 changes: 23 additions & 4 deletions .github/scripts/check_strict_types.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import atexit

import posthog
from posthog import FeatureFlagEvaluations, FlagValue, Posthog
from posthog.emails import AsyncEmails, Emails, SendEmailResult
from datetime import datetime, timezone

from posthog.emails import AsyncEmails, CancelEmailResult, EmailList, Emails, SendEmailResult, UpdateEmailResult
from posthog.ai.evaluations import (
AsyncOfflineEvaluations,
BooleanScorerConfig,
Expand Down Expand Up @@ -46,15 +48,27 @@ _ = (flag_value, all_flags, enabled, payload, evaluations, active)
emails = Emails("phs_example")
template_send: SendEmailResult = emails.send(to="user@example.com", template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", template_data={"name": "Ada"})
rendered_send: SendEmailResult = emails.send(to="user@example.com", template_key="password-reset", subject="Reset", html="<p>Reset</p>", from_="Acme <security@example.com>", reply_to=["help@example.com"], capture_engagement=False, max_retries=0)
scheduled_send: SendEmailResult = emails.send(to="user@example.com", template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", scheduled_at=datetime(2026, 12, 1, tzinfo=timezone.utc))
scheduled_status: str = scheduled_send.status
scheduled_time: datetime | None = scheduled_send.scheduled_at
moved: UpdateEmailResult = emails.update(scheduled_send.id, scheduled_at="2026-12-02T09:00:00+02:00")
canceled: CancelEmailResult = emails.cancel(scheduled_send.id)
page: EmailList = emails.list(cursor=None, limit=50)
next_page: EmailList | None = emails.list(cursor=page.next) if page.next else None
first_time: datetime = page.results[0].scheduled_at
tracked_send: SendEmailResult = emails.send(to="user@example.com", template_key="welcome", subject="Welcome", html="<p>Hi</p>", tracking={"clicks": True, "engagement": False})
untracked_send: SendEmailResult = emails.send(to="user@example.com", template_key="welcome", subject="Welcome", html="<p>Hi</p>", tracking=False)
not_sensitive_send: SendEmailResult = emails.send(to="user@example.com", template_key="newsletter", subject="News", html="<p>News</p>", sensitive=False)
_ = (template_send, rendered_send, tracked_send, untracked_send)
_ = (template_send, rendered_send, tracked_send, untracked_send, scheduled_status, scheduled_time, moved, canceled, next_page, first_time)


async def async_email_types() -> None:
sent: SendEmailResult = await AsyncEmails("phs_example").send(to="user@example.com", template_key="welcome", subject="Hi", html="<p>Hi</p>")
_ = sent
async_emails = AsyncEmails("phs_example")
sent: SendEmailResult = await async_emails.send(to="user@example.com", template_key="welcome", subject="Hi", html="<p>Hi</p>", scheduled_at="2026-12-01T09:00:00Z")
moved: UpdateEmailResult = await async_emails.update(sent.id, scheduled_at=datetime(2026, 12, 2, tzinfo=timezone.utc))
canceled: CancelEmailResult = await async_emails.cancel(sent.id)
page: EmailList = await async_emails.list()
_ = (sent, moved, canceled, page)


async def offline_evaluation_types(
Expand Down Expand Up @@ -114,6 +128,11 @@ Emails("phs_example").send(to="user@example.com", template_id="0190f6d2-7c1e-7b3
Emails("phs_example").send(to="user@example.com", template_key="welcome", subject="Hi", html="<p>Hi</p>", tracking={"open": False}) # expected-type-error
Emails("phs_example").send(to="user@example.com", template_key="welcome", subject="Hi", html="<p>Hi</p>", template_data={"name": "Ada"}) # expected-type-error
Emails("phs_example").send(to="user@example.com", template_key="welcome", subject="Hi", html="<p>Hi</p>", cc="team@example.com") # expected-type-error
Emails("phs_example").send(to="user@example.com", template_key="welcome", subject="Hi", html="<p>Hi</p>", scheduled_at=1796115600) # expected-type-error
Emails("phs_example").update("0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b") # expected-type-error
Emails("phs_example").update("0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", "2026-12-01T09:00:00Z") # expected-type-error
Emails("phs_example").list("scheduled") # expected-type-error
Emails("phs_example").list(status="sent") # expected-type-error


async def invalid_async_email() -> None:
Expand Down
5 changes: 5 additions & 0 deletions .sampo/changesets/scheduled-email-sends.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pypi/posthog: minor
---

Schedule a transactional email with `scheduled_at` on `emails.send()`, up to 30 days ahead, and move, cancel or list scheduled emails with `emails.update()`, `emails.cancel()` and `emails.list()`. A naive `datetime` raises `ValueError` before any request. Send results now carry `status` and `scheduled_at`.
Loading
Loading