Skip to content

Receiving and inboxes (0.9.0) - #16

Merged
heliocosta-dev merged 6 commits into
mainfrom
feat/inboxes
Oct 16, 2026
Merged

heliocosta-dev merged 6 commits into
mainfrom
feat/inboxes

Conversation

@heliocosta-dev

Copy link
Copy Markdown
Member

Adds receiving and inboxes to the SDK, matching the API from MillionSend/millionsend v0.8.0 (receiving) and MillionSend/millionsend#112 (inboxes). The SDK version becomes 0.9.0.

  • Received mail: list (with the wait long-poll and the inbox, recipient, sender, subject, unread and quarantine filters), get, reply, forward, delete and attachments.
  • Inboxes: create, list, get, update and delete, plus safety settings, the latest verification code, quarantine and release, labels, threads with their emails, and drafts with send and reject.
  • Send results: replies, forwards and draft sends return the sent email, or on 202 the draft waiting for approval, kept apart from a sent result.
  • Related resources:
    • webhooks take inbox_ids, and the event list has email.received and the 14 inbox.* events;
    • API keys take inbox_id;
    • domains take receiving_catch_all.
  • Ids: empty, . and .. ids are refused before any request is made.

The SDK was checked against the API's OpenAPI document and route code on the product branch: paths, query and body field names, nullability, and the 202 branch. This SDK's tests and linters pass locally.

Merge and release once the product release with inboxes is deployed.

🤖 Generated with Claude Code

heliocosta-dev and others added 6 commits October 12, 2026 03:39
…s, safety, quarantine and codes (0.9.0)

ms.receiving() lists, reads, replies to, forwards and erases received mail,
with the long-poll wait (the request timeout stretches by the wait) and the
held ids. ms.inboxes() covers inbox CRUD plus safety(), codes(),
quarantine(), labels(), threads() with emails(), and drafts().

Reply, forward and draft send return a SendResult: a 202 from the API is
the draft the inbox's send rules held for approval. Domain gains
receivingCatchAll, API keys an inboxId, webhooks inboxIds (null removes the
filter), and WebhookEvent knows email.received and the 14 inbox events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
URL normalization resolves an empty, "." or ".." path segment away, so an
id such as ".." turned DELETE /inboxes/{id}/labels/.. into
DELETE /inboxes/{id}/. The HTTP client now checks every path once, and
such a call throws IllegalArgumentException without sending anything.

HeldEmail gains getQuarantineReason(), as the long-poll sends it;
getReason() is pending or quarantined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… emails

- receiving().list: ListReceivedEmailsOptions.quarantined(true) sends
  quarantined=true (held mail only), which the API takes next to
  include_quarantined.
- threads().emails().list/get: overloads with includeQuarantined send
  include_quarantined=true, so an owner view can read the held messages
  of a thread that threads().list(...includeQuarantined) shows.
- README: email.received still reaches endpoints subscribed to all events;
  only the inbox.* events are opt-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MillionSendException.getRetryAfter() is the seconds the response's
retry-after header asks to wait, or null without one (and on client-side
and transport failures). MillionSend sends the header on its 429s (rate
limits, the shared domain's daily cap, the name-review budget) and on
503 name_review_unavailable. The three-argument constructor stays.

Tests pin the header parsing and the 404 of an unknown route: JSON
not_found from current servers, application_error from older ones that
answer in plain text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- inboxes().create takes username (the part before the @) and domainId
  (a domains().list() row instead of emailAddress); codes().latest takes
  since.
- Domain has isShared() (false when a server before v0.14.0 sends no
  shared), and the shared row's getSendingMode() and
  getSendingModeReason(), plain strings.
- Contacts batch errors carry the statusCode and name strict mode would
  answer; null on email batches and older servers.
- Docs: inboxes never expire by default, the shared domain's sending
  rules and refusals, name review (503 name_review_unavailable, 429),
  newest-first lists with incremental sync and dead cursors, retries,
  recipients that can never receive mail, the 998-character subject, the
  read-only shared row, the API key domainId rule, the optional
  quarantined count on thread items, and the MillionSend version each
  feature needs.
- The shared inbox fixture no longer expires, and the e2e contact uses
  the SES mailbox simulator: the API refuses example.com.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@heliocosta-dev
heliocosta-dev merged commit d61762c into main Oct 16, 2026
4 checks passed
@heliocosta-dev
heliocosta-dev deleted the feat/inboxes branch October 16, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant