Skip to main content

letterapp (Python)

PyPI version License: MIT

Official Python client for letter.app - onboarding email drip campaigns for product teams.

pip install letterapp

Requires Python 3.8+. Zero runtime dependencies (standard library only).

Quick start

import os
from letterapp import Letter

letter = Letter(api_key=os.environ["LETTER_API_KEY"])  # Dashboard -> Settings -> API keys

# Tell Letter who your user is (call where users sign up or log in).
letter.identify(
    user_id="user_123",
    email="alice@example.com",
    traits={"name": "Alice", "plan": "free"},
)

# Report something they did.
letter.track(user_id="user_123", event="Signed Up", properties={"source": "web"})

# Required before the process exits so no events are lost.
letter.close()

Or use it as a context manager, which flushes on exit:

with Letter(api_key=os.environ["LETTER_API_KEY"]) as letter:
    letter.track(user_id="user_123", event="Workspace Created")

Serverless (Lambda, Cloud Functions)

There is no background time to flush in a serverless handler, so set flush_at=1 and flush() at the end of each invocation:

letter = Letter(api_key=os.environ["LETTER_API_KEY"], flush_at=1)

def handler(event, context):
    letter.track(user_id="user_123", event="Checkout Started")
    letter.flush()

Transactional email

send() mails one person right now: a receipt, a password reset, a verification link. It is never batched and never waits for flush().

result = letter.send(
    to="alice@example.com",
    subject="Reset your password",
    html="<p>Click <a href='https://...'>here</a> to reset.</p>",
    tag="password-reset",
    idempotency_key=f"password-reset:{token}",
)

result["messageId"]  # provider id, appears in delivery events

Only to, subject and one of html / text are required. from_email defaults to the project's sender and must be on a verified domain. A plain-text part is derived from the HTML when you don't supply one.

Pass an idempotency_key whenever the call can be retried (a queue worker, a webhook handler): a replay returns the original send rather than mailing the recipient twice, and it's what lets the SDK retry a 5xx safely.

Failures raise LetterError with .status, .code and .reason. The reason is what tells "this recipient is unreachable" apart from "our account is blocked":

try:
    letter.send(to=to, subject=subject, html=html)
except LetterError as err:
    if err.reason == "suppressed":
        return  # hard-bounced or complained; nothing to fix
    raise

Transactional mail ignores marketing unsubscribes (an opted-out user still gets their password reset) but respects bounces, complaints, and addresses suppressed by hand.

What it does

  • Auto-batching - identify / group / track are queued and flushed every 100ms or 50 events by a background daemon thread. send always goes out immediately.
  • Retries - 429 waits Retry-After; 5xx and network errors back off exponentially with jitter, up to max_retries (default 3). A send without an idempotency_key is never retried, since a duplicate email is worse than a failed one.
  • Idempotent - every ingestion call gets a UUID message_id so retries are deduplicated server-side; send takes your own key.
  • No dependencies - HTTP over the standard library urllib.

API

Letter(
    api_key,
    base_url="https://api.letter.app",  # only set for self-hosted / local
    flush_at=50,                          # 1 for serverless
    flush_interval=0.1,                   # seconds
    max_retries=3,
    timeout=10.0,
    on_error=None,                        # callback(Exception) for bg errors
)

letter.identify(user_id, email=None, traits=None, timezone=None, timestamp=None, message_id=None)
letter.group(user_id, account_id, name=None, traits=None, timestamp=None, message_id=None)
letter.track(user_id, event, properties=None, timestamp=None, message_id=None)
letter.send(to, subject, html=None, text=None, from_email=None, from_name=None,
            reply_to=None, headers=None, tag=None, metadata=None,
            idempotency_key=None)  # -> dict, sent immediately
letter.flush()   # send queued calls now, block until done
letter.close()   # flush + stop the background thread (also runs at exit)

Configuration errors and non-retryable API responses raise LetterError (with .status, .code, .reason and .body). Background transport errors are passed to on_error instead, since they cannot be raised to the caller.

Full documentation

License

MIT - see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

letterapp-0.2.0.tar.gz (8.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

letterapp-0.2.0-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file letterapp-0.2.0.tar.gz.

File metadata

  • Download URL: letterapp-0.2.0.tar.gz
  • Upload date:
  • Size: 8.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for letterapp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2a04a22ffbc65a8ac45346f9b92ea843a5ef2f4899f0723389679553f25bee54
MD5 2fbfe7bb0409e9ea8aa75bda9e118525
BLAKE2b-256 78005e43c262a121ceff12fbdc95d91ff91dde5c6a82ca1f7787a51b465bedc7

See more details on using hashes here.

File details

Details for the file letterapp-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: letterapp-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for letterapp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 82b2bef836466c7b2e274f8174cb23e67408331181f40a903e33c005a4f1e35a
MD5 5bb16e4ce5e0a60495f64aa635b01adb
BLAKE2b-256 eabfd89ca908e99c2fde13023dc3ed79afea164d8fd0b594f6faa289f5b01c01

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page