Repository navigation
Fixes and additions from live API verification - #41
Merged
Merged
Conversation
…led status send_bulk() raised a ValidationError after Postmark had accepted the request: the response model read the request ID as "ID", but the API returns "Id", so the caller never got the bulk request ID back. Read "Id" (still accepting "ID"), and expose the other fields the API returns (subject, total_messages, percentage_completed, released_count, failed_count) on both BulkSendResponse and BulkSendStatus. Add BulkJobStatus.CANCELLED, which Postmark documents. FAILED is kept so existing code that references it keeps working.
The batch endpoints answer HTTP 200 even when individual entries fail, and a failed entry carries only ErrorCode and Message. SendResponse required To, SubmittedAt and MessageID, so a single failed entry raised a ValidationError and discarded the results of every entry that was sent. Make the three fields optional so every entry comes back, in order, and the caller checks success. This applies to send_batch() and send_batch_with_template(). Typing only: SendResponse.to, .submitted_at and .message_id are now optional. Successful sends still populate them.
get_delivery_stats() raised a ValidationError whenever Postmark returned its "All" total entry, which has only a Name and a Count. Make type and type_code optional. Typing only: BounceTypeCount.type and .type_code are now optional.
Server IDs are integers, but PushTemplatesRequest typed them as str, so a real server ID raised InvalidEmailException before any request was made. Postmark accepts either form, so accept int or str and send the value as given. Typing only: the two ID fields are now int | str.
Postmark leaves out any category whose count is zero, for example a day with only desktop clicks has no Mobile or Unknown entry. The per-day models required every field, so platform_usage(), browser_platform_usage() and click_location() raised on real data, and the other day models were exposed to the same problem. Default every per-day count to 0, and every days list to empty.
list_opens() and list_clicks() sent client_name, client_company, client_family, os_name, os_family and os_company in camelCase. Postmark ignores unknown query parameters, so these filters silently did nothing and always returned unfiltered results. Send the names the API understands. Behaviour change: calls that pass these filters now return filtered results. The Python argument names are unchanged.
Postmark has no date filter on the opens and clicks endpoints, so the from_date and to_date arguments of list_opens() and list_clicks() never had any effect. Keep accepting them so existing calls do not break, but stop sending them and emit a FutureWarning that points at the caller. Behaviour change: a call that passes either argument now warns. Results are unchanged.
The docstring promised that only the fields you pass are changed, but
PUT /webhooks/{id} requires Url and verifies the endpoint using only the
request body, so an edit without the URL failed, and an edit of a webhook that
uses Basic auth failed verification.
Read the current webhook first, merge the requested changes into it (triggers
field by field, dropping the read-only per-trigger Status) and send the
complete configuration.
Behaviour change: edit() now makes one extra GET before the PUT.
Add GET /email/bulk. Unlike the other lists it paginates with an opaque cursor rather than offsets, so add a generic CursorPage (items and next_key) and have list_bulk_requests() return it. stream_bulk_requests() follows the cursor. count is validated to 1-500 before any request: Postmark answers 0 or a negative count with an HTTP 500, which the client would retry.
Add GET /webhooks/{id}/statistics: delivery metrics over the last 24 hours,
overall and per trigger, plus the verification status of each enabled trigger.
Averages are optional because Postmark omits them when there was no traffic or
no retries.
Add POST /webhooks/{id}/verify, which sends a test event for every enabled
trigger and reports how the endpoint answered. A failed verification is
returned with success=False and per-trigger details, as Postmark answers
HTTP 200, and is not raised.
The docstring notes that each call sends real requests and that timeouts are
retried by the client, which would send them again.
Add the layoutTemplate query parameter, which restricts the list to templates that use the Layout template with a given alias. It is the last parameter, so existing positional calls are unaffected.
By default Postmark verifies the endpoint before saving a webhook and refuses to save it if the test events fail. The way to skip that is the query parameter ?verify=false; a Verify field in the body is silently ignored. Add verify=True to create() and edit(), and send ?verify=false when it is False. The webhook is then saved with its triggers unverified and receives no events until verify() succeeds.
Postmark reports "verified" or "unverified" on each enabled trigger, but the trigger models dropped it. Add an optional status to all six. Disabled triggers have none.
Add tests for the remaining untested code paths: inbound and server edit filters, the suppression batch limit, exception fallbacks by HTTP status, error-code parsing, pagination, PATCH and DELETE delegation on both clients, the retry-loop guard, closing the sync clients, the Django backend's no-op paths and the package version fallback.
paginate() yields every item it fetches and then advances the offset by that many, so the offset always equals the number of items yielded, and max_records is capped at 10,000. offset + current_limit can therefore never pass the 10,000 limit and the guard could never run. Remove it and add a test that the final request still ends at exactly 10,000.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Running the SDK against the live Postmark API showed several places where it disagreed with the real responses. This PR fixes those bugs and adds three missing endpoints. It has 16 commits, one per change, and each commit passes format, lint, types and the full test suite on its own, so please use "Rebase and merge" to keep them separate.
Fixes
send_bulk()readsIdandBulkJobStatusgainsCANCELLEDValidationErrorafter Postmark had accepted the requestget_delivery_stats()accepts theAllentryTypeorTypeCode, so the call always raisedtemplates.push()accepts integer server IDs0from_date/to_datewarn and are no longer sentwebhooks.edit()works for partial updatesUrlon every update and verifies from the request body onlyAdditions
outbound.list_bulk_requests()andstream_bulk_requests(), with a new genericCursorPagewebhooks.get_statistics()webhooks.verify()templates.list(layout_template=...)verify=Falseonwebhooks.create()andwebhooks.edit()(sent as?verify=false)statuson the webhook trigger modelsCommits 15 and 16 add tests for previously untested code (coverage is now 100%) and remove one unreachable branch in
paginate().Compatibility
Nothing is removed or renamed, and new arguments were added at the end of signatures. Please look at these:
list_opens()/list_clicks()withfrom_dateorto_datenow emit aFutureWarning. Results are unchanged. A test run with warnings turned into errors would fail on such a call.webhooks.edit()now makes oneGETbefore thePUT. Code that mocks the HTTP layer needs a response for it.SendResponse.to/.submitted_at/.message_idandBounceTypeCount.type/.type_codeare now optional, andPushTemplatesRequestserver IDs areint | str. Successful calls still fill them in.These are listed in the
[Unreleased]section ofCHANGELOG.md. The version bump is deliberately not in this PR; it will follow in a separate release PR.Verification
Notes for review
.secrets.baselinechanged slightly: the line number of one already-approved placeholder moved (41 to 48), the timestamp updated, and the scanner added one internal filter entry on its own. No new secrets were added.CHANGELOG.mdandtests/django_backend/test_backend.py; both sides were kept.