Skip to main content

django-helsinki-suomifi-messages

Suomi.fi Messages client for City of Helsinki Django apps. See the API reference for endpoint details.

  • Send electronic messages to recipients with active Suomi.fi mailboxes
  • Send multichannel messages (electronic or paper mail, depending on mailbox status)
  • Send paper mail without a personal identity code or business ID
  • Check whether recipients have active Suomi.fi mailboxes
  • Retrieve message events and read received messages
  • Upload and download attachments

Contents

Installation

Requires Python 3.10+ and Django 5.2+.

pip install django-helsinki-suomifi-messages

No changes to INSTALLED_APPS are needed; this is a pure client library.

Configuration

Add the following to your Django settings:

# Suomi.fi API credentials
SUOMIFI_USERNAME = "your-api-username"
SUOMIFI_PASSWORD = "your-api-password"
SUOMIFI_SERVICE_ID = "your-service-id"

# Posti Messaging Oy credentials (required for paper mail only)
# Obtained during paper mail deployment:
# https://kehittajille.suomi.fi/services/messages/deployment/deployment-of-the-printing-enveloping-and-distribution-service
SUOMIFI_POSTI_EMAIL = "your-posti-email"
SUOMIFI_POSTI_USERNAME = "your-posti-username"
SUOMIFI_POSTI_PASSWORD = "your-posti-password"

All settings default to an empty string if not set. SUOMIFI_USERNAME, SUOMIFI_PASSWORD, and SUOMIFI_SERVICE_ID can also be passed directly to the relevant client methods. Posti credentials (SUOMIFI_POSTI_*) must always be configured in Django settings.

Quick start

from suomifi_messages import SuomiFiClient
from suomifi_messages.schemas import Address, BodyFormat

client = SuomiFiClient()  # QA environment; use type="prod" for production
client.login()            # uses SUOMIFI_USERNAME / SUOMIFI_PASSWORD from settings

recipient_address = Address(
    name="Matti Meikäläinen",
    street_address="Esimerkkikatu 1",
    zip_code="00100",
    city="Helsinki",
    country_code="FI"
)
sender_address = Address(
    name="Helsingin kaupunki",
    street_address="Lähettäjänkatu 1",
    zip_code="00100",
    city="Helsinki",
    country_code="FI"
)

with open("letter.pdf", "rb") as f:
    attachment_id = client.upload_attachment("letter.pdf", f)

message_id, external_id = client.send_multichannel_message(
    title="This is a title",
    body="Hello, world!",
    body_format=BodyFormat.TEXT,
    recipient_id="123456-789A",
    recipient_address=recipient_address,
    sender_address=sender_address,
    paper_mail_attachment_id=attachment_id,
)

Usage

Creating a client

from suomifi_messages import SuomiFiClient

# QA environment (default)
client = SuomiFiClient()

# More explicit
client = SuomiFiClient(type="qa")

# Production environment
client = SuomiFiClient(type="prod")

Authentication

# Uses SUOMIFI_USERNAME and SUOMIFI_PASSWORD from Django settings
client.login()

# Or pass credentials explicitly
client.login(username="user", password="pass")

# Change password; re-login required afterwards (token is invalidated)
client.change_password(current_password="old-pass", new_password="new-pass")

Checking mailbox status

# Check multiple recipients at once
active_ids = client.check_mailboxes(["123456-789A", "987654-321B"])

# Check a single recipient
has_mailbox = client.check_mailbox("123456-789A")  # True / False

Sending a multichannel message

A multichannel message is delivered electronically to recipients with an active mailbox, or as paper mail to those without:

from suomifi_messages.schemas import Address, BodyFormat

recipient_address = Address(
    name="Matti Meikäläinen",
    street_address="Esimerkkikatu 1",
    zip_code="00100",
    city="Helsinki",
    country_code="FI"
)
sender_address = Address(
    name="Helsingin kaupunki",
    street_address="Lähettäjänkatu 1",
    zip_code="00100",
    city="Helsinki",
    country_code="FI"
)

# Upload the paper mail attachment first
with open("letter.pdf", "rb") as f:
    attachment_id = client.upload_attachment("letter.pdf", f)

message_id, external_id = client.send_multichannel_message(
    title="Title",
    body="Hello, world!",
    body_format=BodyFormat.TEXT,  # or BodyFormat.MARKDOWN
    recipient_id="123456-789A",
    recipient_address=recipient_address,
    sender_address=sender_address,
    paper_mail_attachment_id=attachment_id,
)

All send methods return a (message_id, external_id) tuple:

  • message_id: the Suomi.fi identifier assigned to the message. Use this to reply to the message or to look it up via get_message().
  • external_id: your own identifier for the message, used for idempotency. If not provided, a UUID is generated automatically. Sending a message with the same external_id twice will raise SuomiFiDuplicateMessageError.

Sending an electronic message

Use this to send a new message or reply to a message from an end user:

message_id, external_id = client.send_electronic_message(
    title="Title",
    body="Hello, world!",
    body_format=BodyFormat.TEXT,
    recipient_id="123456-789A",
    reply_to=original_message_id,
    reply_allowed=True,
)

Sending paper mail without an identity code

Use this when the recipient's identity code is not known or not required. The recipient is identified solely by their postal address. Using the same Address setup as above:

with open("letter.pdf", "rb") as f:
    attachment_id = client.upload_attachment("letter.pdf", f)

message_id, external_id = client.send_paper_mail_without_id(
    recipient_address=recipient_address,
    sender_address=sender_address,
    attachment_id=attachment_id,
)

Reading events and messages

# Fetch events; pass continuation_token back in subsequent calls for pagination
events, continuation_token = client.get_events()
for event in events:
    message = client.get_message(event.metadata.message_id)
    # process message...

# Download an attachment from a received message
content = client.get_attachment(attachment_id)  # bytes

Error handling

from suomifi_messages import (
    SuomiFiAPIError,
    SuomiFiClientError,
    SuomiFiDuplicateMessageError,
    SuomiFiServerError,
)

try:
    message_id, external_id = client.send_electronic_message(...)
except SuomiFiDuplicateMessageError as e:
    # 409: message with this external_id already sent
    # e.message_id contains the original message ID
    existing_id = e.message_id
except SuomiFiClientError as e:
    # 4xx: bad request, mailbox not active, etc.
    print(e.response_body)
except SuomiFiServerError as e:
    # 5xx: retry later
    raise
except SuomiFiAPIError as e:
    # Unexpected non-2xx
    raise

The exception hierarchy is:

SuomiFiError
└── SuomiFiAPIError
    ├── SuomiFiClientError
    │   └── SuomiFiDuplicateMessageError
    └── SuomiFiServerError

For developers

Prerequisites

Testing

Run the tests with:

hatch test

Test all environments in the matrix with:

hatch test -a

Available Hatch scripts

Command Description Example
hatch run test <args> Run pytest directly hatch run test -k login
hatch run lint Install and run pre-commit hooks hatch run lint
hatch run manage <args> Run Django management commands hatch run manage migrate

Metadata

Release files for django-helsinki-suomifi-messages 1.0.0

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

Source distribution (sdist)

Source distribution for django-helsinki-suomifi-messages 1.0.0
File Size Uploaded Provenance Actions
django_helsinki_suomifi_messages-1.0.0.tar.gz 26.9 kB With provenance Details for django_helsinki_suomifi_messages-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-helsinki-suomifi-messages 1.0.0
File Interpreter ABI Platform Actions
django_helsinki_suomifi_messages-1.0.0-py3-none-any.whl Python 3 none any Details for django_helsinki_suomifi_messages-1.0.0-py3-none-any.whl

Total release size: 43.6 kB

Release files / django_helsinki_suomifi_messages-1.0.0.tar.gz

Download URL django_helsinki_suomifi_messages-1.0.0.tar.gz
Size 26.9 kB
Tags Source
SHA-256 checksum
How to use checksums
1e4d86f2e0da31db5d45535f8fd21265e09c3656e5f3e4a05ea155e88c15e811
BLAKE2b-256 checksum
How to use checksums
123e390cb18035409675caf5e123a688e433975005d2526d8327257eb550b633
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 19, 2026.

Transparency log

Release files / django_helsinki_suomifi_messages-1.0.0-py3-none-any.whl

Download URL django_helsinki_suomifi_messages-1.0.0-py3-none-any.whl
Size 16.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
550c409eec3f06b6e2814d69822fddcec7942fa5806e2da65ec4561b65ff2e69
BLAKE2b-256 checksum
How to use checksums
7c1530b8e584f07aa1c2efe167046ec7dfa941a6b38735de2afd3c8c78a04602
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 19, 2026.

Transparency log

Security for django-helsinki-suomifi-messages 1.0.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_helsinki_suomifi_messages-1.0.0.tar.gz

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 Mar 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

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