Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /python-sdk-quickstart.md.
Verified · 8/11/2026

Install and Configure the Python SDK

Install primitivedotdev, create a PrimitiveClient with your API key, and send your first email in a few lines of Python.

Install primitivedotdev, create a client with your API key, and send your first outbound email. Requires Python >=3.10.

Note

This page covers the Python SDK. For the Node.js SDK, see Node.js SDK Quickstart; for Go, see Go SDK Quickstart. All three implement the same inbound/outbound email model, so pick by language, not by capability.

  1. 1

    Install the package#

    pip install primitivedotdev
    

    The import name is primitive (distinct from the PyPI package name primitivedotdev).

  2. 2

    Set your API key#

    Get a key from your dashboard and export it:

    export PRIMITIVE_API_KEY=prim_test
    

    Read it from the environment when constructing the client, rather than hardcoding it:

    import os
    import primitive
    
    client = primitive.client(api_key=os.environ["PRIMITIVE_API_KEY"])
    
  3. 3

    Send your first email#

    import os
    import primitive
    
    client = primitive.client(api_key=os.environ["PRIMITIVE_API_KEY"])
    
    result = client.send(
        from_email="Support <support@example.com>",
        to="alice@example.com",
        subject="Hello",
        body_text="Hi there",
    )
    
    print(result.id, result.status, result.queue_id, result.delivery_status)
    
  4. 4

    Verify the result#

    A successful call returns a SendResult dataclass. By default send returns as soon as Primitive accepts the message for delivery, so result.delivery_status is None at this point. The printed line looks like this (ids will differ):

    <send-id> submitted_to_agent <queue-id> None
    

    To confirm the actual SMTP outcome (delivered, bounced, deferred, or wait_timeout) instead of just acceptance, pass wait=True; see Sending Email for the full wait-mode contract and delivery-status meanings.

Configuring the client#

primitive.client(...) returns a PrimitiveClient. The constructor accepts:

ParameterPurpose
api_keyRequired. Your Primitive API key.
api_base_url_1Primary API host. Defaults to DEFAULT_API_BASE_URL_1.
api_base_url_2Attachment-capable host used for send/reply. Defaults to DEFAULT_API_BASE_URL_2.
**client_kwargsForwarded to the underlying AuthenticatedClient, including timeout in seconds.
import primitive

client = primitive.client(
    api_key="prim_test",
    timeout=60.0,  # seconds; raise this when you plan to pass wait=True
)
Note

Internally the SDK is a dual-host client: most operations hit api_base_url_1, while send and reply route to api_base_url_2, which accepts larger request bodies for attachments. The split is transparent; you never choose the host yourself, and both defaults point at production.

Warning

PrimitiveClient no longer accepts a bare base_url keyword. Passing it raises a TypeError explaining the rename to api_base_url_1 / api_base_url_2.

Per-call and default timeouts#

Every send, reply, and forward call (and their a* async variants) accepts per-call timeout, extra_headers, and idempotency_key keyword arguments. Use client.with_options(...) to change the client-wide defaults without repeating them on every call:

fast = client.with_options(timeout=5.0)
fast.send(
    from_email="support@example.com",
    to="alice@example.com",
    subject="Hello",
    body_text="Hi there",
)

Per-call kwargs still win over with_options defaults, and with_options accepts only timeout and extra_headers; idempotency_key is rejected as a client default. Full details live on Client and Request Options.

Next call: receive and reply#

Sending is half the story. primitive.receive(...) turns an inbound webhook into a normalized ReceivedEmail:

import primitive

client = primitive.client(api_key="prim_test")


def webhook_handler(body: bytes, headers: dict[str, str]) -> dict[str, object]:
    email = primitive.receive(
        body=body,
        headers=headers,
        secret="whsec_...",
    )

    client.reply(email, "Thank you for your email.")
    return {"ok": True}

The normalized email object and the receive/send/reply/forward flow are explained once, for every SDK, on Inbound and Outbound Email Model. For the Python-specific mechanics of receiving and parsing, see Receiving and Parsing Inbound Email; for sending in depth, see Sending Email.

Tip

If an AI coding agent is doing the integration, point it at the Agent Guide instead: install commands, canonical API shapes, and repo conventions on one page.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer