# Send automation notifications from Python

Send Python project results to one central inbox, with bounded timeouts and errors that preserve context for people and agents.

A notification client can stay small while moving project results out of chat and into one durable record. Keep authentication outside the payload, bound the request and preserve enough structure for people and agents to understand the outcome.

Published: 2026-07-31. Updated: 2026-08-24.

## Send JSON with the standard library

**Bounded Python request**

```python
import json
import os
import urllib.error
import urllib.request

NOTIFICATION_PAYLOAD = {
  "title": "Research run completed",
  "body": "The agent finished with 18 verified records and 2 rejected rows.",
  "severity": "success",
  "source": "research-agent",
  "tags": [
    "agent",
    "daily-run"
  ],
  "deduplicationKey": "research-run:2026-07-31",
  "metadata": {
    "accepted": "18",
    "rejected": "2",
    "runId": "run_2026_07_31"
  },
  "actions": [
    {
      "id": "open-run",
      "type": "link",
      "label": "Open run",
      "href": "https://example.com/runs/run_2026_07_31",
      "style": "primary"
    }
  ]
}
request = urllib.request.Request(
    f"{os.environ['FYINBOX_URL']}/api/v1/notifications",
    data=json.dumps(NOTIFICATION_PAYLOAD).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {os.environ['FYINBOX_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)

try:
    with urllib.request.urlopen(request, timeout=10) as response:
        result = json.load(response)
except urllib.error.HTTPError as error:
    detail = error.read().decode("utf-8", errors="replace")
    raise RuntimeError(f"notification rejected ({error.code}): {detail}") from error

print(result["id"], result["created"])
```

> **Keep failure handling local.** Decide whether a failed notification should fail the worker, be retried with the same deduplication key or be recorded for later delivery. Do not retry every 4xx response blindly.

## Use a key derived from the event

The example derives a stable key from the logical run date. A retry can resend the same payload safely; a genuinely new run receives a different key.

- Use a workflow run identifier when the producer already has one.
- Include the event type when one run can emit several distinct results.
- Keep the key stable across transport retries but change it for a new logical event.

## Frequently asked questions

### Does the producer need a special integration?

No. The examples use an authenticated HTTPS request, so any script, service or visual workflow that can send HTTP can use the same contract.

### Should credentials be included in notification data?

No. Keep credentials in the producer's secret store and send only the context needed to understand or locate the event.

### Why send this to FYInbox instead of chat or email?

FYInbox gives project notifications one structured, reviewable home instead of scattering them through Slack, Telegram and email. Those tools remain useful for conversations and intentional messages.

### Why structure notifications for people and agents?

People and agents both need stable source, outcome and next-action context. FYInbox keeps that context in one notification record outside conversation streams.

[Guides index](/guides)
