Skip to main content

Puzzel SMS Gateway Python Client

GitHub License Latest Version Supported Python versions Downloads Coverage GitHub issues GitHub last commit

Copyright 2024-2026 Puzzel AS

Contents

Overview

This is a Python client for the Puzzel SMS Gateway API. It provides a type-safe, fluent interface for sending SMS messages and managing message batches.

The client is generated from the SMS Gateway OpenAPI specification and uses the Kiota abstractions for HTTP communication, serialization, and authentication.

Prerequisites

  • Python 3.10 or higher
  • pip or uv package manager

Dependencies

The package automatically installs all required dependencies when you install it from PyPI:

  • microsoft-kiota-abstractions>=1.0.0 - Core Kiota abstractions
  • microsoft-kiota-http>=1.0.0 - HTTP request handling
  • microsoft-kiota-serialization-json>=1.0.0 - JSON serialization
  • microsoft-kiota-serialization-text>=1.0.0 - Text serialization
  • microsoft-kiota-serialization-form>=1.0.0 - Form serialization
  • microsoft-kiota-serialization-multipart>=1.0.0 - Multipart serialization
  • httpx>=0.24.0 - Async HTTP client

You don't need to install these manually - they're included automatically. See pyproject.toml for the complete list.

Installation

uv is a fast Python package installer and resolver.

Option 1: Install into Existing Project

# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install the SMS Gateway client from PyPI
uv pip install puzzel-sms-gateway-client

Option 2: Create New Project with uv

# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a new project
uv init <projectname> --app --python 3.13.1
cd <projectname>

# Create virtual environment
uv venv --python 3.13.1 --seed
# Activate virtual environment
source .venv/bin/activate  # On macOS/Linux
# or
.venv\Scripts\activate     # On Windows
# Verify installation
python -V
uv pip list

# Install the SMS Gateway client from PyPI
uv pip install puzzel-sms-gateway-client

Note: uv init typically creates .venv/ automatically, but you can use --seed to explicitly ensure that pip, setuptools, and wheel are installed.

Using pip

pip install puzzel-sms-gateway-client

Quick Start

import asyncio
from kiota_abstractions.authentication import AnonymousAuthenticationProvider
from kiota_http.httpx_request_adapter import HttpxRequestAdapter
from src.mt_http_client import MtHttpClient
from src.models.gateway_request import GatewayRequest
from src.models.message import Message

async def send_sms():
    # Set up authentication and HTTP adapter
    auth_provider = AnonymousAuthenticationProvider()
    request_adapter = HttpxRequestAdapter(auth_provider)
    request_adapter.base_url = "https://smsgw.puzzel.com"

    # Create the client
    client = MtHttpClient(request_adapter)

    # Create a message
    gateway_request = GatewayRequest(
        service_id=YOUR_SERVICE_ID,  # Your Puzzel service ID
        username="YOUR_USERNAME",     # Your Puzzel username
        password="YOUR_PASSWORD",     # Your Puzzel password
        message=[
            Message(
                recipient="+47xxxxxxxxx",  # Phone number with country code
                content="Hello World!"
            )
        ]
    )

    # Send the message
    response = await client.gw.rs.send_messages.post(gateway_request)

    print(f"Batch Reference: {response.batch_reference}")
    for status in response.message_status:
        print(f"Message ID: {status.message_id}")
        print(f"Status: {status.status_message}")

# Run the async function
asyncio.run(send_sms())

Authentication

The SMS Gateway uses Basic Authentication passed through the request body. You need to provide:

  • service_id: Your service ID (integer)
  • username: Your gateway username (string)
  • password: Your gateway password (string)

These credentials are included in the GatewayRequest object for each API call.

Base URL Configuration

The Puzzel SMS Gateway base URL is: https://smsgw.puzzel.com

Important: Set only the base domain, not including any path:

# ✅ Correct
request_adapter.base_url = "https://smsgw.puzzel.com"

# ❌ Wrong - don't include the path
request_adapter.base_url = "https://smsgw.puzzel.com/gw/rs"

The client automatically constructs the full URL path based on the API method you call.

from kiota_abstractions.authentication import AnonymousAuthenticationProvider
from kiota_http.httpx_request_adapter import HttpxRequestAdapter

# Use AnonymousAuthenticationProvider since credentials are in the request body
auth_provider = AnonymousAuthenticationProvider()
request_adapter = HttpxRequestAdapter(auth_provider)
request_adapter.base_url = "https://smsgw.puzzel.com"

Usage Examples

Basic Message Sending

Send a simple SMS to a single recipient:

import asyncio
from kiota_abstractions.authentication import AnonymousAuthenticationProvider
from kiota_http.httpx_request_adapter import HttpxRequestAdapter
from src.mt_http_client import MtHttpClient
from src.models.gateway_request import GatewayRequest
from src.models.message import Message

async def send_basic_sms():
    auth_provider = AnonymousAuthenticationProvider()
    request_adapter = HttpxRequestAdapter(auth_provider)
    request_adapter.base_url = "https://smsgw.puzzel.com"

    client = MtHttpClient(request_adapter)

    request = GatewayRequest(
        service_id=12345,
        username="your_username",
        password="your_password",
        message=[
            Message(
                recipient="+47xxxxxxxxx",
                content="Hello from Puzzel SMS Gateway!"
            )
        ]
    )

    response = await client.gw.rs.send_messages.post(request)
    return response

asyncio.run(send_basic_sms())

Sending with Message Settings

Send an SMS with advanced settings like send window, originator, and priority:

import asyncio
from src.mt_http_client import MtHttpClient
from src.models.gateway_request import GatewayRequest
from src.models.message import Message
from src.models.settings import Settings
from src.models.send_window import SendWindow
from src.models.originator_settings import OriginatorSettings
from src.models.originator_type import OriginatorType

async def send_scheduled_sms():
    from kiota_abstractions.authentication import AnonymousAuthenticationProvider
    from kiota_http.httpx_request_adapter import HttpxRequestAdapter
    from src.mt_http_client import MtHttpClient

    auth_provider = AnonymousAuthenticationProvider()
    request_adapter = HttpxRequestAdapter(auth_provider)
    request_adapter.base_url = "https://smsgw.puzzel.com"

    client = MtHttpClient(request_adapter)

    request = GatewayRequest(
        service_id=12345,
        username="your_username",
        password="your_password",
        message=[
            Message(
                recipient="+47xxxxxxxxx",
                content="Scheduled message",
                settings=Settings(
                    priority=1,
                    validity=173,
                    originator_settings=OriginatorSettings(
                        originator="1960",
                        originator_type=OriginatorType.Network
                    ),
                    send_window=SendWindow(
                        start_date="2026-03-21",
                        start_time="09:00:00",
                        stop_date="2026-03-21",
                        stop_time="17:00:00"
                    )
                )
            )
        ]
    )

    response = await client.gw.rs.send_messages.post(request)
    return response

asyncio.run(send_scheduled_sms())

Advanced Configuration

Send an SMS with all available options:

import asyncio
from src.models.gateway_request import GatewayRequest
from src.models.message import Message
from src.models.settings import Settings
from src.models.gas_settings import GasSettings
from src.models.parameter import Parameter

async def send_advanced_sms():
    from kiota_abstractions.authentication import AnonymousAuthenticationProvider
    from kiota_http.httpx_request_adapter import HttpxRequestAdapter
    from src.mt_http_client import MtHttpClient

    auth_provider = AnonymousAuthenticationProvider()
    request_adapter = HttpxRequestAdapter(auth_provider)
    request_adapter.base_url = "https://smsgw.puzzel.com"

    client = MtHttpClient(request_adapter)

    request = GatewayRequest(
        service_id=12345,
        username="your_username",
        password="your_password",
        batch_reference="my-batch-001",
        message=[
            Message(
                recipient="+47xxxxxxxxx",
                content="Advanced message",
                price=100,
                client_reference="msg-ref-001",
                settings=Settings(
                    priority=1,
                    validity=173,
                    differentiator="campaign-spring-2026",
                    invoice_node="marketing-dept",
                    age=18,
                    new_session=True,
                    session_id="session-12345",
                    auto_detect_encoding=True,
                    gas_settings=GasSettings(
                        service_code="02001",
                        description="Premium SMS"
                    ),
                    parameter=[
                        Parameter(key="custom_key", value="custom_value")
                    ]
                )
            )
        ]
    )

    response = await client.gw.rs.send_messages.post(request)
    return response

asyncio.run(send_advanced_sms())

Batch Management

List, retrieve, and stop message batches:

import asyncio

async def manage_batches():
    from kiota_abstractions.authentication import AnonymousAuthenticationProvider
    from kiota_http.httpx_request_adapter import HttpxRequestAdapter
    from src.mt_http_client import MtHttpClient

    auth_provider = AnonymousAuthenticationProvider()
    request_adapter = HttpxRequestAdapter(auth_provider)
    request_adapter.base_url = "https://smsgw.puzzel.com"

    client = MtHttpClient(request_adapter)

    service_id = 12345
    batch_reference = "my-batch-001"

    # List all batches for a service
    batch_list = await client.mgmt.rs.service.by_service_id(service_id).batch.get()
    print(f"Total batches: {len(batch_list.message_batch)}")
    for batch in batch_list.message_batch:
        print(f"Batch: {batch.client_batch_reference}, Total: {batch.total_size}, On Hold: {batch.on_hold}")

    # Get details of a specific batch
    batch_details = await client.mgmt.rs.service.by_service_id(service_id).batch.by_client_batch_reference(batch_reference).get()
    print(f"Batch details: {batch_details.message_batch}")

    # Stop a batch
    stop_response = await client.mgmt.rs.service.by_service_id(service_id).batch.by_client_batch_reference(batch_reference).delete()
    print(f"Stopped {len(stop_response.stopped_messages)} messages")

asyncio.run(manage_batches())

API Reference

Main Client

  • MtHttpClient: The main entry point for the SDK

API Endpoints

The client provides access to different API versions and endpoints:

  • client.gw.rs.send_messages: Send messages (recommended endpoint)
  • client.chimera.send_messages: Alternative send messages endpoint
  • client.mgmt.rs.service: Batch management operations
  • client.management: Alternative batch management endpoint

Models

All models are located in the src/models/ directory:

  • GatewayRequest: The main request object for sending messages
  • GatewayResponse: Response containing batch reference and message statuses
  • Message: Individual SMS message
  • Settings: Message-level settings
  • MessageStatus: Status information for sent messages
  • SendWindow: Schedule messages for specific time windows
  • OriginatorSettings: Configure message sender information
  • GasSettings: GAS (Gateway API Service) configuration
  • Parameter: Custom key-value parameters
  • BatchListResponse: List of message batches
  • BatchSingleResponse: Single batch details
  • StopBatchResponse: Response when stopping a batch

Request Builders

Request builders provide a fluent API for constructing requests:

  • SendMessagesRequestBuilder: Build and execute send message requests
  • BatchRequestBuilder: Build batch management requests
  • Various service-specific builders in the src/ directory structure

Error Handling

The client uses Kiota's error handling mechanisms. HTTP errors are mapped to specific exception types:

import asyncio
from src.models.problem_details import ProblemDetails

async def send_with_error_handling():
    try:
        response = await client.gw.rs.send_messages.post(request)
        print(f"Success! Batch: {response.batch_reference}")
    except Exception as e:
        if hasattr(e, 'error') and isinstance(e.error, ProblemDetails):
            print(f"API Error: {e.error.title}")
            print(f"Status: {e.error.status}")
            print(f"Detail: {e.error.detail}")
        else:
            print(f"Unexpected error: {e}")

asyncio.run(send_with_error_handling())

Common error status codes:

  • 401 Unauthorized: Invalid credentials
  • 404 Not Found: Batch or resource not found
  • 400 Bad Request: Invalid request parameters

Testing

Install dev dependencies and run the full test suite:

uv sync
uv run pytest

Run with coverage:

uv run pytest --cov=src --cov-report=term-missing

The tests live in tests/unit/ and cover client initialization, all data models (field access, serialization, deserialization keys), and the request builders. See docs/07-testing.md for the full testing guide, including how to run specific tests, use fixtures, and write new tests.

Migration from v2.x

If you're migrating from the previous puzzel_sms_gateway_client v2.x, here are the key differences:

1. Async/Await Required

The Kiota client is fully asynchronous:

Old (v2.x):

response = client.send(messages=[message])

New (Kiota):

response = await client.gw.rs.send_messages.post(request)

2. Client Initialization

Old (v2.x):

import puzzel_sms_gateway_client as smsgw

client = smsgw.Client(
    service_id=SERVICE_ID,
    username=USERNAME,
    password=PASSWORD,
    base_address=BASE_ADDRESS,
)

New (Kiota):

from kiota_abstractions.authentication import AnonymousAuthenticationProvider
from kiota_http.httpx_request_adapter import HttpxRequestAdapter
from src.mt_http_client import MtHttpClient

auth_provider = AnonymousAuthenticationProvider()
request_adapter = HttpxRequestAdapter(auth_provider)
request_adapter.base_url = BASE_ADDRESS

client = MtHttpClient(request_adapter)

3. Credentials in Request

Credentials are now part of each request rather than the client:

Old (v2.x):

client = smsgw.Client(service_id=..., username=..., password=...)
client.send(messages=[...])

New (Kiota):

request = GatewayRequest(
    service_id=SERVICE_ID,
    username=USERNAME,
    password=PASSWORD,
    message=[...]
)
await client.gw.rs.send_messages.post(request)

4. Model Structure

Models are now dataclasses with explicit typing:

Old (v2.x):

message = smsgw.Message(
    recipient="+47...",
    content="Hello",
    settings=smsgw.MessageSettings(...)
)

New (Kiota):

from src.models.message import Message
from src.models.settings import Settings

message = Message(
    recipient="+47...",
    content="Hello",
    settings=Settings(...)
)

5. Fluent API

The new client uses a fluent, path-based API:

# Send messages
await client.gw.rs.send_messages.post(request)

# Manage batches
await client.mgmt.rs.service.by_service_id(123).batch.get()
await client.mgmt.rs.service.by_service_id(123).batch.by_client_batch_reference("ref").get()
await client.mgmt.rs.service.by_service_id(123).batch.by_client_batch_reference("ref").delete()

Publishing

To build and publish the package to PyPI using uv:

# 1. Bump the version in pyproject.toml, then build
uv build

# 2. Publish to TestPyPI first (optional but recommended)
uv publish \
  --publish-url https://test.pypi.org/legacy/ \
  --token pypi-your-test-token-here

# 3. Publish to PyPI
uv publish --token pypi-your-production-token-here

For full details — credential storage, TestPyPI validation, version conventions, and ~/.pypirc setup — see docs/08-publishing.md.

Additional Documentation

For more detailed information, see the documentation in the docs/ folder:

Support

For issues, questions, or contributions, please contact Puzzel support or refer to the main SMS Gateway documentation.

License

Released under the MIT license. See LICENSE for details.

Metadata

Release files for puzzel-sms-gateway-client 3.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for puzzel-sms-gateway-client 3.0.1
File Size Uploaded
puzzel_sms_gateway_client-3.0.1.tar.gz 79.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for puzzel-sms-gateway-client 3.0.1
File Interpreter ABI Platform
puzzel_sms_gateway_client-3.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 111.5 kB

Release files / puzzel_sms_gateway_client-3.0.1.tar.gz

Download URL puzzel_sms_gateway_client-3.0.1.tar.gz
Size 79.9 kB
Tags Source
SHA-256 checksum
How to use checksums
8f3dc2f59ac02ecfe3de8afba1b4199a0077240f291c6cb1034a37f1f4ac08fd
BLAKE2b-256 checksum
How to use checksums
2d7cba857c334e5f4b92d3e153eab46c9dc2a13ea768597afa8b0225c5adbaa6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 17, 2026.

Transparency log

Release files / puzzel_sms_gateway_client-3.0.1-py3-none-any.whl

Download URL puzzel_sms_gateway_client-3.0.1-py3-none-any.whl
Size 31.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b9e975b20cecfc7ff207fdd14858f0709aefa88b2167146034514a76025ebece
BLAKE2b-256 checksum
How to use checksums
3b18010fd301b4eb0865220a1723fd1b60c44e567a3e8bfde0fd038f32c18708
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.0.1 This release

2 release files

3.0.0

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release 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