Skip to main content

Official Python SDK for PossiNote API

Project description

Possinote Python SDK

Official Python SDK for the PossiNote API - Send SMS, emails, and schedule messages with ease.

Installation

From PyPI

pip install possinote

From Source

git clone https://github.com/possitech/possinote-python.git
cd possinote-python
pip install -e .

Quick Start

from possinote import Possinote

# Initialize the client with your API key
client = Possinote(api_key='your_api_key_here')

# Send a single SMS
response = client.sms.send(
    to='+233244123456',
    message='Hello from Possinote!',
    sender_id='YourSenderID'
)

# Send a single email
response = client.email.send(
    recipient='user@example.com',
    subject='Welcome to Possinote',
    content='<h1>Hello!</h1><p>Welcome to our platform.</p>',
    sender_name='Your Company'
)

API Reference

Authentication

All API requests require authentication using your API key:

client = Possinote(api_key='your_api_key_here')

SMS Operations

Send Single SMS

response = client.sms.send(
    to='+233244123456',
    message='Your message here',
    sender_id='YourSenderID'
)

# Response
{
    "success": True,
    "data": {
        "message_id": "msg_123456789",
        "to": "+233244123456",
        "status": "queued",
        "cost": 1.0,
        "created_at": "2025-08-11T11:30:00Z"
    }
}

Send Bulk SMS

response = client.sms.send_bulk(
    sender_id='YourSenderID',
    messages=[
        {'to': '+233244123456', 'message': 'Message 1'},
        {'to': '+233244123457', 'message': 'Message 2'}
    ]
)

# Response
{
    "success": True,
    "data": {
        "batch_id": "batch_123456789",
        "total_messages": 2,
        "successful": 2,
        "failed": 0,
        "total_cost": 2.0,
        "messages": [
            {"message_id": "msg_1", "to": "+233244123456", "status": "queued"},
            {"message_id": "msg_2", "to": "+233244123457", "status": "queued"}
        ]
    }
}

Schedule Single SMS

response = client.sms.schedule(
    recipient='+233244123456',
    message='Scheduled message',
    sender_id='YourSenderID',
    scheduled_at='2025-08-11T12:00:00Z'
)

# Response
{
    "success": True,
    "data": {
        "id": "schedule_123456789",
        "recipient": "+233244123456",
        "message": "Scheduled message",
        "scheduled_at": "2025-08-11T12:00:00Z",
        "status": "pending",
        "cost": "1.0"
    }
}

Schedule Bulk SMS

response = client.sms.schedule_bulk(
    sender_id='YourSenderID',
    messages=[
        {'recipient': '+233244123456', 'message': 'Scheduled message 1'},
        {'recipient': '+233244123457', 'message': 'Scheduled message 2'}
    ],
    scheduled_at='2025-08-11T12:00:00Z'
)

# Response
{
    "success": True,
    "data": {
        "batch_id": "batch_123456789",
        "scheduled_count": 2,
        "total_cost": 2.0,
        "scheduled_at": "2025-08-11T12:00:00Z",
        "messages": [
            {"id": "schedule_1", "recipient": "+233244123456", "status": "pending"},
            {"id": "schedule_2", "recipient": "+233244123457", "status": "pending"}
        ]
    }
}

Email Operations

Send Single Email

response = client.email.send(
    recipient='user@example.com',
    subject='Welcome Email',
    content='<h1>Welcome!</h1><p>Thank you for joining us.</p>',
    sender_name='Your Company'
)

# Response
{
    "success": True,
    "message": "Email queued for delivery",
    "recipient": "user@example.com",
    "message_id": "email_123456789"
}

Send Bulk Email

response = client.email.send_bulk(
    subject='Newsletter',
    content='<h1>Newsletter</h1><p>This is our monthly newsletter.</p>',
    recipients=['user1@example.com', 'user2@example.com'],
    sender_name='Your Company'
)

# Response
{
    "success": True,
    "message": "Bulk emails queued for delivery",
    "queued_count": 2,
    "total_count": 2,
    "batch_id": "batch_123456789",
    "emails": [
        {"message_id": "email_1", "recipient": "user1@example.com", "status": "queued"},
        {"message_id": "email_2", "recipient": "user2@example.com", "status": "queued"}
    ]
}

Scheduling Operations

Schedule Single Email

response = client.scheduling.schedule_email(
    recipient='user@example.com',
    subject='Scheduled Email',
    content='<h1>Scheduled Content</h1>',
    scheduled_at='2025-08-11T12:00:00Z',
    sender_name='Your Company'
)

# Response
{
    "success": True,
    "data": {
        "id": "email_schedule_123456789",
        "recipient": "user@example.com",
        "subject": "Scheduled Email",
        "scheduled_at": "2025-08-11T12:00:00Z",
        "status": "pending",
        "cost": "1.0"
    }
}

Schedule Bulk Individual Emails

response = client.scheduling.schedule_multiple_emails([
    {
        'recipient': 'user1@example.com',
        'subject': 'Personalized Email 1',
        'content': '<h1>Hello User 1!</h1>',
        'scheduled_at': '2025-08-11T12:00:00Z',
        'sender_name': 'Your Company'
    },
    {
        'recipient': 'user2@example.com',
        'subject': 'Personalized Email 2',
        'content': '<h1>Hello User 2!</h1>',
        'scheduled_at': '2025-08-11T12:00:00Z',
        'sender_name': 'Your Company'
    }
])

# Response
{
    "success": True,
    "data": {
        "batch_id": "batch_123456789",
        "total_scheduled": 2,
        "total_cost": 2.0,
        "scheduled_emails": [
            {"id": "email_1", "recipient": "user1@example.com", "status": "pending"},
            {"id": "email_2", "recipient": "user2@example.com", "status": "pending"}
        ]
    }
}

Framework Integration

Django Integration

1. Install the Package

pip install possinote

2. Configure in Settings

# settings.py
import os
from possinote import Possinote

# Initialize the client
POSSINOTE_CLIENT = Possinote(api_key=os.environ.get('POSSINOTE_API_KEY'))

# Or use Django settings
POSSINOTE_CLIENT = Possinote(api_key=os.environ.get('POSSINOTE_API_KEY', 'your_api_key_here'))

3. Use in Views

# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
import json
from possinote.exceptions import (
    AuthenticationError, PaymentRequiredError, 
    RateLimitError, ValidationError, APIError
)
from .models import User
from django.conf import settings

@csrf_exempt
@require_http_methods(["POST"])
def send_sms(request):
    try:
        data = json.loads(request.body)
        response = settings.POSSINOTE_CLIENT.sms.send(
            to=data['phone_number'],
            message=data['message'],
            sender_id='YourBrand'
        )
        
        if response['success']:
            return JsonResponse({
                'message': 'SMS sent successfully',
                'data': response['data']
            })
        else:
            return JsonResponse({
                'error': 'Failed to send SMS'
            }, status=400)
            
    except AuthenticationError:
        return JsonResponse({'error': 'Authentication failed'}, status=401)
    except PaymentRequiredError:
        return JsonResponse({'error': 'Insufficient credits'}, status=402)
    except ValidationError as e:
        return JsonResponse({'error': str(e)}, status=400)
    except Exception as e:
        return JsonResponse({'error': 'An error occurred'}, status=500)

@csrf_exempt
@require_http_methods(["POST"])
def send_bulk_sms(request):
    try:
        data = json.loads(request.body)
        response = settings.POSSINOTE_CLIENT.sms.send_bulk(
            sender_id='YourBrand',
            messages=data['messages']  # List of {'to': phone, 'message': text}
        )
        
        return JsonResponse({
            'message': 'Bulk SMS sent',
            'data': response['data']
        })
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

@csrf_exempt
@require_http_methods(["POST"])
def send_email(request):
    try:
        data = json.loads(request.body)
        response = settings.POSSINOTE_CLIENT.email.send(
            recipient=data['email'],
            subject=data['subject'],
            content=data['content'],
            sender_name='Your Company'
        )
        
        return JsonResponse({
            'message': 'Email sent successfully',
            'data': response
        })
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

4. Use in Services

# services.py
from django.conf import settings
from django.template.loader import render_to_string
from django.urls import reverse
from .models import User, Appointment
import logging

logger = logging.getLogger(__name__)

class NotificationService:
    def __init__(self):
        self.client = settings.POSSINOTE_CLIENT
    
    def send_welcome_sms(self, user: User):
        """Send welcome SMS to new user"""
        try:
            response = self.client.sms.send(
                to=user.phone_number,
                message=f"Welcome {user.first_name}! Your account has been created successfully.",
                sender_id='YourBrand'
            )
            logger.info(f"Welcome SMS sent to {user.email}: {response}")
            return response
        except Exception as e:
            logger.error(f"Failed to send welcome SMS to {user.email}: {e}")
            raise
    
    def send_password_reset_email(self, user: User, reset_token: str):
        """Send password reset email"""
        try:
            reset_url = f"https://yourdomain.com{reverse('password_reset_confirm', kwargs={'token': reset_token})}"
            
            html_content = render_to_string('emails/password_reset.html', {
                'user': user,
                'reset_url': reset_url
            })
            
            response = self.client.email.send(
                recipient=user.email,
                subject='Password Reset Request',
                content=html_content,
                sender_name='Your Company'
            )
            logger.info(f"Password reset email sent to {user.email}")
            return response
        except Exception as e:
            logger.error(f"Failed to send password reset email to {user.email}: {e}")
            raise
    
    def send_bulk_newsletter(self, users: list, newsletter_content: dict):
        """Send bulk newsletter SMS"""
        try:
            messages = [
                {
                    'to': user.phone_number,
                    'message': f"Newsletter: {newsletter_content['title']}"
                }
                for user in users
            ]
            
            response = self.client.sms.send_bulk(
                sender_id='YourBrand',
                messages=messages
            )
            logger.info(f"Bulk newsletter sent to {len(users)} users")
            return response
        except Exception as e:
            logger.error(f"Failed to send bulk newsletter: {e}")
            raise
    
    def schedule_reminder_email(self, appointment: Appointment):
        """Schedule appointment reminder email"""
        try:
            html_content = render_to_string('emails/appointment_reminder.html', {
                'appointment': appointment,
                'user': appointment.user
            })
            
            response = self.client.scheduling.schedule_email(
                recipient=appointment.user.email,
                subject='Appointment Reminder',
                content=html_content,
                scheduled_at=appointment.datetime.isoformat(),
                sender_name='Your Company'
            )
            logger.info(f"Appointment reminder scheduled for {appointment.user.email}")
            return response
        except Exception as e:
            logger.error(f"Failed to schedule appointment reminder: {e}")
            raise

5. Use in Celery Tasks

# tasks.py
from celery import shared_task
from django.conf import settings
from .models import User, Appointment
from .services import NotificationService

@shared_task
def send_welcome_sms_task(user_id: int):
    """Celery task to send welcome SMS"""
    try:
        user = User.objects.get(id=user_id)
        service = NotificationService()
        service.send_welcome_sms(user)
    except User.DoesNotExist:
        print(f"User with id {user_id} not found")
    except Exception as e:
        print(f"Failed to send welcome SMS: {e}")

@shared_task
def send_password_reset_email_task(user_id: int, reset_token: str):
    """Celery task to send password reset email"""
    try:
        user = User.objects.get(id=user_id)
        service = NotificationService()
        service.send_password_reset_email(user, reset_token)
    except User.DoesNotExist:
        print(f"User with id {user_id} not found")
    except Exception as e:
        print(f"Failed to send password reset email: {e}")

@shared_task
def send_bulk_newsletter_task(user_ids: list, newsletter_content: dict):
    """Celery task to send bulk newsletter"""
    try:
        users = User.objects.filter(id__in=user_ids)
        service = NotificationService()
        service.send_bulk_newsletter(users, newsletter_content)
    except Exception as e:
        print(f"Failed to send bulk newsletter: {e}")

@shared_task
def schedule_appointment_reminder_task(appointment_id: int):
    """Celery task to schedule appointment reminder"""
    try:
        appointment = Appointment.objects.get(id=appointment_id)
        service = NotificationService()
        service.schedule_reminder_email(appointment)
    except Appointment.DoesNotExist:
        print(f"Appointment with id {appointment_id} not found")
    except Exception as e:
        print(f"Failed to schedule appointment reminder: {e}")

# Usage in views
from .tasks import send_welcome_sms_task, send_password_reset_email_task

def register_user(request):
    # ... user registration logic ...
    user = User.objects.create(...)
    
    # Send welcome SMS asynchronously
    send_welcome_sms_task.delay(user.id)
    
    return JsonResponse({'message': 'User registered successfully'})

def request_password_reset(request):
    # ... password reset logic ...
    token = generate_reset_token()
    
    # Send password reset email asynchronously
    send_password_reset_email_task.delay(user.id, token)
    
    return JsonResponse({'message': 'Password reset email sent'})

6. URL Configuration

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('api/send-sms/', views.send_sms, name='send_sms'),
    path('api/send-bulk-sms/', views.send_bulk_sms, name='send_bulk_sms'),
    path('api/send-email/', views.send_email, name='send_email'),
]

7. Environment Configuration

# .env
POSSINOTE_API_KEY=your_api_key_here
# settings.py
from decouple import config

POSSINOTE_CLIENT = Possinote(api_key=config('POSSINOTE_API_KEY'))

8. Email Templates

<!-- templates/emails/password_reset.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Password Reset</title>
</head>
<body>
    <h1>Password Reset Request</h1>
    <p>Hello {{ user.first_name }},</p>
    <p>You requested a password reset. Click the link below to reset your password:</p>
    <a href="{{ reset_url }}">Reset Password</a>
    <p>This link will expire in 1 hour.</p>
</body>
</html>
<!-- templates/emails/appointment_reminder.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Appointment Reminder</title>
</head>
<body>
    <h1>Appointment Reminder</h1>
    <p>Hello {{ user.first_name }},</p>
    <p>This is a reminder for your appointment:</p>
    <ul>
        <li><strong>Date:</strong> {{ appointment.datetime|date:"F d, Y" }}</li>
        <li><strong>Time:</strong> {{ appointment.datetime|time:"g:i A" }}</li>
        <li><strong>Location:</strong> {{ appointment.location }}</li>
    </ul>
</body>
</html>

Error Handling

The SDK provides specific exception classes for different error types:

from possinote.exceptions import (
    PossinoteError,
    AuthenticationError,
    PaymentRequiredError,
    RateLimitError,
    ValidationError,
    APIError
)

try:
    response = client.sms.send(
        to='+233244123456',
        message='Hello',
        sender_id='SenderID'
    )
except AuthenticationError as e:
    print(f"Authentication failed: {e}")
except PaymentRequiredError as e:
    print(f"Payment required: {e}")
except RateLimitError as e:
    print(f"Rate limit exceeded: {e}")
except ValidationError as e:
    print(f"Validation error: {e}")
except APIError as e:
    print(f"API error: {e}")

Error Types

  • AuthenticationError - Invalid API key (401)
  • PaymentRequiredError - Insufficient credits (402)
  • RateLimitError - Rate limit exceeded (429)
  • ValidationError - Invalid request data (400)
  • APIError - Other API errors

Configuration

Base URL

The SDK uses the production API by default. For testing, you can modify the base URL:

# In possinote/client.py
BASE_URL = 'https://notifyapi.possitech.net/api/v1'

Timeout Settings

HTTP requests use default timeout settings. You can customize them in the client:

# The SDK uses requests defaults
# You can modify timeout settings in possinote/client.py

Requirements

  • Python >= 3.7
  • requests library
  • typing (included in Python 3.5+)

Development

Setup Development Environment

# Clone the repository
git clone https://github.com/possitech/possinote-python.git
cd possinote-python

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt

# Install in development mode
pip install -e .

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=possinote

# Run specific test file
pytest tests/test_sms.py

Code Quality

# Format code
black possinote/

# Lint code
flake8 possinote/

# Type checking
mypy possinote/

Building and Publishing

# Build package
python setup.py sdist bdist_wheel

# Test upload to TestPyPI
twine upload --repository testpypi dist/*

# Upload to PyPI
twine upload dist/*

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/possitech/possinote-python. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The package is available as open source under the terms of the MIT License.

Support

For support, email support@possitech.net or visit our documentation.

Changelog

1.0.0

  • Initial release
  • SMS sending and scheduling
  • Email sending and scheduling
  • Comprehensive error handling
  • Full API coverage

Project details


Download files

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

Source Distribution

possinote-1.0.0.tar.gz (14.1 kB view details)

Uploaded Source

Built Distribution

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

possinote-1.0.0-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file possinote-1.0.0.tar.gz.

File metadata

  • Download URL: possinote-1.0.0.tar.gz
  • Upload date:
  • Size: 14.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for possinote-1.0.0.tar.gz
Algorithm Hash digest
SHA256 14aa6473a1f647f899ba048a0b2fd668c030a81ba1469fa172ed023c8bfefb59
MD5 69a7078cb8667e2914e00559ddf50319
BLAKE2b-256 6a66d412403703a24f3c37e9c91681168466c4378d7f516f4ae1cc0008bcd37a

See more details on using hashes here.

File details

Details for the file possinote-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: possinote-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for possinote-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1ac7feb8857dd77a08015e4b7c40ac14e3f56d783e43ebf25931ff1dd002cede
MD5 b4989dc58f9031ac8eefde4644836f62
BLAKE2b-256 f7882806ce7760194956e1266d0d5ab352728d0eac2a38ac5f60d9e3f8b00800

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page