Applies To: ■ PoliteMail Desktop ■ PoliteMail Online ■ PoliteMail M365
Version: ■ 4.9 ■ 5.0 ■ 5.1+
Pulling Send Data using the API
The sample below uses Python to pull sent message stats, and the sample output is also provided. Note that you will need to get your own Personal Access Token, and substitute 'prerelease.pmail2.com' with your own PoliteMail hostname.
import os
import json
import argparse
import datetime as dt
import requests
import logging
import time
def iso(s: str) -> str:
return dt.datetime.fromisoformat(s).isoformat()
def build_base(host: str) -> str:
host = host.strip()
host = host.replace("https://", "").replace("http://", "")
return f"https://{host}/api/odata"
def odata_get(session: requests.Session, url: str, params: dict) -> dict:
logging.info("Starting HTTP GET %s", url)
logging.debug("Params: %s", params)
start = time.time()
try:
r = session.get(url, params=params, timeout=30)
except requests.exceptions.RequestException:
logging.exception("Request failed for %s", url)
raise
elapsed = time.time() - start
logging.info("HTTP %s %s in %.2fs", r.status_code, url, elapsed)
if not r.ok:
logging.error("Response body: %s", r.text)
raise RuntimeError(f"HTTP {r.status_code}: {r.text}")
try:
return r.json()
except ValueError:
logging.exception("Failed to decode JSON from %s", url)
raise
def fetch_sentmessages(base: str, session: requests.Session, since: str | None, until: str | None, top: int, max_requests: int = 1000) -> list[dict]:
url = f"{base}/SentMessages"
select_fields = ",".join([
"ID","Subject","SentDate","OwnerID","RecipientCount",
"OpenCount","ClickCount","ReadCount","EngagementRate",
"AvgReadTime","MostRecentOpenDate"
])
filters = []
if since:
filters.append(f"SentDate ge {iso(since)}")
if until:
filters.append(f"SentDate lt {iso(until)}")
params = {
"$select": select_fields,
"$orderby": "SentDate desc",
"$top": top
}
if filters:
params["$filter"] = " and ".join(filters)
out = []
skip = 0
max_pages = int(os.getenv("POLITEMAIL_MAX_PAGES", "1000"))
page_count = 0
request_count = 0
while True:
page_count += 1
if page_count > max_pages:
logging.warning("Reached max_pages=%s, stopping to avoid infinite loop", max_pages)
break
params["$skip"] = skip
logging.info("Fetching page skip=%s top=%s", skip, top)
if request_count >= max_requests:
logging.info("Reached max_requests=%s, stopping further requests", max_requests)
break
data = odata_get(session, url, params)
request_count += 1
rows = data.get("value", [])
logging.info("Received %d rows", len(rows))
out.extend(rows)
if len(rows) < top:
break
skip += top
return out
def main():
p = argparse.ArgumentParser()
p.add_argument("--host", default=os.getenv("POLITEMAIL_HOST"))
p.add_argument("--pat", default=os.getenv("POLITEMAIL_PAT"))
p.add_argument("--since", default=None)
p.add_argument("--until", default=None)
p.add_argument("--top", type=int, default=1)
p.add_argument("--max-requests", type=int, default=int(os.getenv("POLITEMAIL_MAX_REQUESTS", "1")),
help="Maximum number of HTTP requests to make (default 1 or POLITEMAIL_MAX_REQUESTS)")
args = p.parse_args()
if not args.host or not args.pat:
raise SystemExit("Set POLITEMAIL_HOST and POLITEMAIL_PAT, or pass --host and --pat")
base = build_base(args.host)
# configure logging: send logs to stderr so stdout remains usable for JSON
log_level = logging.DEBUG if os.getenv("POLITEMAIL_DEBUG") else logging.INFO
logging.basicConfig(level=log_level, format="%(asctime)s %(levelname)s %(message)s")
logging.info("Using base URL: %s", base)
s = requests.Session()
s.auth = ("pat", args.pat)
s.headers.update({"Accept": "application/json"})
logging.info("Starting fetch_sentmessages (top=%s since=%s until=%s max_requests=%s)", args.top, args.since, args.until, args.max_requests)
rows = fetch_sentmessages(base, s, args.since, args.until, args.top, args.max_requests)
# print JSON to stdout and flush immediately
print(json.dumps(rows, indent=2, default=str), flush=True)
if __name__ == "__main__":
main()
PS C:\code\api-samples\python> python politemail_sentmessage_stats.py --host prerelease.pmail2.com --pat REDACTED2026-01-19 08:46:47,107 INFO Using base URL: https://prerelease.pmail2.com/api/odata2026-01-19 08:46:47,108 INFO Starting fetch_sentmessages (top=1 since=None until=None max_requests=1)2026-01-19 08:46:47,108 INFO Fetching page skip=0 top=12026-01-19 08:46:47,108 INFO Starting HTTP GET https://prerelease.pmail2.com/api/odata/SentMessages2026-01-19 08:46:54,430 INFO HTTP 200 https://prerelease.pmail2.com/api/odata/SentMessages in 7.32s2026-01-19 08:46:54,430 INFO Received 1 rows2026-01-19 08:46:54,430 INFO Fetching page skip=1 top=12026-01-19 08:46:54,431 INFO Reached max_requests=1, stopping further requests[{"ID": 25937,"Subject": "Message (Prerelease)","SentDate": "2026-01-19T06:58:11.363Z","OwnerID": 7,"RecipientCount": 1,"AvgReadTime": 30000.0,"ClickCount": 0.0,"MostRecentOpenDate": "2026-01-19T07:37:15.837Z","OpenCount": 1.0,"ReadCount": 1.0,"EngagementRate": 1.0}]PS C:\code\api-samples\python>
Note the result returns some basic information on the operation, followed by the specific details about the message in a JSON format, including the subject, send date, recipient count, average read time, click count, and more.