Skip to content

Commit 0411133

Browse files
committed
Add Loyalty and Referrals SDK support
1 parent 9787811 commit 0411133

3 files changed

Lines changed: 139 additions & 29 deletions

File tree

‎README.md‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,56 @@
11
# Yournotify Python
22

3-
Official Python SDK for the current Yournotify API.
3+
Python SDK for the current Yournotify API.
4+
5+
## Install
6+
7+
```bash
8+
pip install yournotify-python
9+
```
10+
11+
## Quickstart
12+
13+
```python
14+
from yournotify import Yournotify, YournotifyApiError
15+
16+
client = Yournotify("YOURNOTIFY_API_KEY")
17+
18+
try:
19+
client.validate_auth()
20+
client.send_email(
21+
"Welcome campaign",
22+
"Welcome",
23+
"<p>Hello</p>",
24+
"Hello",
25+
"draft",
26+
"noreply@smtp.yournotify.net",
27+
[{"email": "person@example.com", "name": "Person"}],
28+
)
29+
except YournotifyApiError as exc:
30+
print(exc.status_code, exc.body)
31+
```
32+
33+
## Campaigns
34+
35+
```python
36+
client.create_campaign({"name": "WhatsApp", "channel": "whatsapp", "template_id": "template_123"})
37+
client.send_sms("SMS", "YTAuth", "Hello", "draft", [{"telephone": "+2348100000000"}])
38+
client.send_whatsapp({"name": "WhatsApp", "template_id": "template_123"})
39+
client.send_push({"name": "Push", "title": "Hello", "body": "Welcome"})
40+
client.send_in_app({"name": "In-app", "title": "Hello", "body": "Welcome"})
41+
client.test_campaign({"channel": "email", "email": "person@example.com"})
42+
```
43+
44+
## Contacts, Lists, Rewards, Automation
45+
46+
```python
47+
client.add_contact("person@example.com", "+2348100000000", [123], "Person", {"source": "python"})
48+
client.get_contacts({"page": 1})
49+
client.add_list("Newsletter", "public", "single")
50+
51+
client.create_reward({"name": "Referral reward"})
52+
client.send_reward({"reward_id": 123, "email": "person@example.com"})
53+
54+
client.identify({"external_id": "user_123", "email": "person@example.com"})
55+
client.track({"event": "checkout.completed", "external_id": "user_123"})
56+
```

‎yournotify/__init__.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
from .client import Yournotify
1+
from .client import Yournotify, YournotifyApiError
2+
3+
__all__ = ["Yournotify", "YournotifyApiError"]

‎yournotify/client.py‎

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,99 @@
11
from __future__ import annotations
2+
3+
from typing import Any
4+
25
import requests
6+
7+
8+
class YournotifyApiError(Exception):
9+
def __init__(self, message: str, status_code: int, body: Any) -> None:
10+
super().__init__(message)
11+
self.status_code = status_code
12+
self.body = body
13+
14+
15+
def _normalize_list(value: Any, key: str) -> list[dict[str, Any]]:
16+
if value is None:
17+
return []
18+
items = value if isinstance(value, list) else [value]
19+
return [{key: item} if isinstance(item, str) else item for item in items]
20+
21+
322
class Yournotify:
423
def __init__(self, api_key: str, api_url: str = "https://api.yournotify.com/") -> None:
24+
if not api_key or not api_key.strip():
25+
raise ValueError("Yournotify API key is required.")
526
self.api_key = api_key
627
self.api_url = api_url.rstrip("/") + "/"
7-
def set_api_url(self, api_url: str): self.api_url = api_url.rstrip("/") + "/"; return self
8-
def request(self, endpoint: str, method: str = "GET", data: dict | None = None) -> dict:
28+
29+
def set_api_url(self, api_url: str):
30+
self.api_url = api_url.rstrip("/") + "/"
31+
return self
32+
33+
def request(self, endpoint: str, method: str = "GET", data: dict[str, Any] | None = None) -> dict[str, Any]:
934
url = self.api_url + endpoint.lstrip("/")
10-
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
11-
kwargs = {"headers": headers, "timeout": 30}
35+
headers = {"Authorization": f"Bearer {self.api_key}", "Accept": "application/json"}
36+
kwargs: dict[str, Any] = {"headers": headers, "timeout": 30}
1237
method = method.upper()
13-
if method == "GET": kwargs["params"] = data or {}
14-
elif data is not None: kwargs["json"] = data
15-
return requests.request(method, url, **kwargs).json()
16-
def send_email(self, name, subject, html, text="", status="draft", from_email="", to=None):
17-
lists = to if isinstance(to, list) else ([{"email": to}] if to else [])
18-
return self.request("campaigns/email", "POST", {"name": name, "subject": subject, "html": html, "body": html, "text": text, "from": from_email, "from_email": from_email, "status": status, "channel": "email", "lists": lists})
19-
def send_sms(self, name, from_sender, text, status="draft", to=None):
20-
lists = to if isinstance(to, list) else ([{"telephone": to}] if to else [])
21-
return self.request("campaigns/sms", "POST", {"name": name, "from": from_sender, "sender": from_sender, "text": text, "body": text, "status": status, "channel": "sms", "lists": lists})
22-
def add_contact(self, email=None, telephone=None, list_id=None, name="", attribs=None):
23-
lists = [] if list_id is None else (list_id if isinstance(list_id, list) else [list_id])
24-
return self.request("contacts", "POST", {"email": email, "telephone": telephone, "lists": lists, "name": name, "attribs": attribs or {}})
38+
if method == "GET":
39+
kwargs["params"] = data or {}
40+
elif data is not None:
41+
headers["Content-Type"] = "application/json"
42+
kwargs["json"] = data
43+
response = requests.request(method, url, **kwargs)
44+
body = response.json() if response.content else {}
45+
if not response.ok:
46+
raise YournotifyApiError(body.get("message") or f"Yournotify API request failed with status {response.status_code}.", response.status_code, body)
47+
return body
48+
49+
def validate_auth(self): return self.request("auth/me")
50+
def get_profile(self): return self.validate_auth()
51+
def create_campaign(self, data=None): return self.request("campaigns", "POST", data or {})
52+
def send_email(self, name, subject, html, text="", status="draft", from_email="", to=None): return self.create_campaign({"name": name, "subject": subject, "html": html, "body": html, "text": text, "from": from_email, "from_email": from_email, "status": status, "channel": "email", "lists": _normalize_list(to, "email")})
53+
def send_sms(self, name, from_sender, text, status="draft", to=None): return self.create_campaign({"name": name, "from": from_sender, "sender": from_sender, "text": text, "body": text, "status": status, "channel": "sms", "lists": _normalize_list(to, "telephone")})
54+
def send_whatsapp(self, data=None): return self.create_campaign({**(data or {}), "channel": "whatsapp"})
55+
def send_push(self, data=None): return self.create_campaign({**(data or {}), "channel": "push"})
56+
def send_inapp(self, data=None): return self.create_campaign({**(data or {}), "channel": "inapp"})
57+
def test_campaign(self, data=None): return self.request("campaigns/test", "POST", data or {})
58+
def get_campaign(self, campaign_id): return self.request(f"campaigns/{campaign_id}")
59+
def get_campaigns(self, channel=None, params=None): return self.request("campaigns", "GET", ({"channel": channel} if channel else {}) | (params or {}))
60+
def update_campaign(self, campaign_id, data=None): return self.request(f"campaigns/{campaign_id}", "PUT", data or {})
61+
def delete_campaign(self, campaign_id): return self.request(f"campaigns/{campaign_id}", "DELETE")
62+
def get_campaign_stats(self, campaign_id, channel="email"): return self.request(f"campaigns/{campaign_id}/analytics/stats", "GET", {"channel": channel})
63+
def get_campaign_reports(self, campaign_id, channel="email"): return self.request(f"campaigns/{campaign_id}/analytics/reports", "GET", {"channel": channel})
64+
def add_contact(self, email=None, telephone=None, list_id=None, name="", attribs=None): return self.request("contacts", "POST", {"email": email, "telephone": telephone, "lists": [] if list_id is None else (list_id if isinstance(list_id, list) else [list_id]), "name": name, "attribs": attribs or {}})
2565
def get_contact(self, contact_id): return self.request(f"contacts/{contact_id}")
2666
def get_contacts(self, params=None): return self.request("contacts", "GET", params or {})
2767
def update_contact(self, contact_id, email=None, telephone=None, lists=None, name="", attribs=None): return self.request(f"contacts/{contact_id}", "PUT", {"email": email, "telephone": telephone, "lists": lists or [], "name": name, "attribs": attribs or {}})
2868
def delete_contact(self, contact_id): return self.request(f"contacts/{contact_id}", "DELETE")
69+
def validate_contacts(self, data=None): return self.request("contacts/validate", "POST", data or {})
2970
def add_list(self, title, type="public", optin="single"): return self.request("lists", "POST", {"name": title, "title": title, "type": type, "optin": optin})
3071
def get_list(self, list_id): return self.request(f"lists/{list_id}")
3172
def get_lists(self, params=None): return self.request("lists", "GET", params or {})
3273
def update_list(self, list_id, title): return self.request(f"lists/{list_id}", "PUT", {"name": title, "title": title})
3374
def delete_list(self, list_id): return self.request(f"lists/{list_id}", "DELETE")
34-
def get_campaign(self, campaign_id): return self.request(f"campaigns/{campaign_id}")
35-
def get_campaigns(self, channel, params=None): return self.request("campaigns", "GET", {"channel": channel, **(params or {})})
36-
def update_campaign(self, campaign_id, data=None): return self.request(f"campaigns/{campaign_id}", "PUT", data or {})
37-
def delete_campaign(self, campaign_id): return self.request(f"campaigns/{campaign_id}", "DELETE")
38-
def get_campaign_stats(self, ids, channel="email"):
39-
id_param = ",".join(str(v) for v in ids) if isinstance(ids, list) else ids
40-
return self.request("campaigns/analytics/stats", "GET", {"id": id_param, "channel": channel})
41-
def get_campaign_reports(self, ids, channel="email"):
42-
id_param = ",".join(str(v) for v in ids) if isinstance(ids, list) else ids
43-
return self.request("campaigns/analytics/reports", "GET", {"id": id_param, "channel": channel})
44-
def get_profile(self): return self.request("account/profile")
75+
def get_rewards(self, params=None): return self.request("rewards", "GET", params or {})
76+
def create_reward(self, data=None): return self.request("rewards", "POST", data or {})
77+
def send_reward(self, data=None): return self.request("rewards/send", "POST", data or {})
78+
def get_reward_products(self, params=None): return self.request("rewards/products", "GET", params or {})
79+
def get_reward_analytics(self, reward_id): return self.request(f"rewards/{reward_id}/analytics")
80+
def get_reward_submissions(self, reward_id, params=None): return self.request(f"rewards/{reward_id}/submissions", "GET", params or {})
81+
def get_loyalty_programs(self, params=None): return self.request("loyalty/programs", "GET", params or {})
82+
def get_loyalty_program(self, program_id): return self.request(f"loyalty/programs/{program_id}")
83+
def create_loyalty_program(self, data=None): return self.request("loyalty/programs", "POST", data or {})
84+
def update_loyalty_program(self, program_id, data=None): return self.request(f"loyalty/programs/{program_id}", "PUT", data or {})
85+
def get_loyalty_members(self, program_id, params=None): return self.request(f"loyalty/programs/{program_id}/members", "GET", params or {})
86+
def adjust_loyalty_points(self, program_id, data=None): return self.request(f"loyalty/programs/{program_id}/points", "POST", data or {})
87+
def track_loyalty_event(self, program_id, data=None): return self.request(f"loyalty/programs/{program_id}/events", "POST", data or {})
88+
def redeem_loyalty_reward(self, program_id, data=None): return self.request(f"loyalty/programs/{program_id}/redeem", "POST", data or {})
89+
def get_referral_programs(self, params=None): return self.request("referrals/programs", "GET", params or {})
90+
def get_referral_program(self, program_id): return self.request(f"referrals/programs/{program_id}")
91+
def create_referral_program(self, data=None): return self.request("referrals/programs", "POST", data or {})
92+
def update_referral_program(self, program_id, data=None): return self.request(f"referrals/programs/{program_id}", "PUT", data or {})
93+
def delete_referral_program(self, program_id): return self.request(f"referrals/programs/{program_id}", "DELETE")
94+
def get_referral_advocates(self, program_id, params=None): return self.request(f"referrals/programs/{program_id}/advocates", "GET", params or {})
95+
def add_referral_advocate(self, program_id, data=None): return self.request(f"referrals/programs/{program_id}/advocates", "POST", data or {})
96+
def track_referral_event(self, program_id, data=None): return self.request(f"referrals/programs/{program_id}/events", "POST", data or {})
97+
def get_referral_analytics(self, program_id, params=None): return self.request(f"referrals/programs/{program_id}/analytics", "GET", params or {})
98+
def identify(self, data=None): return self.request("automations/identify", "POST", data or {})
99+
def track(self, data=None): return self.request("automations/events", "POST", data or {})

0 commit comments

Comments
 (0)