From 2037c411909cb8539e90150afe23487b666c3603 Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 13:41:23 +1000 Subject: [PATCH 1/7] Document recipient-based outbound billing --- changelog.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/changelog.mdx b/changelog.mdx index 0b443f9..77fb495 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -4,6 +4,17 @@ description: "New features, improvements, and fixes across the Sendmux platform. rss: true --- + + ## Bug fixes + +Outgoing usage now counts each accepted To, CC, and BCC recipient. A message sent to several recipients is billed by recipient, while addresses rejected before acceptance are not charged. + +Blind-copy addresses remain part of delivery without being exposed in the delivered message headers. + +Repeated account setup requests reuse work already in progress. When a newer setup email is issued, its link replaces the earlier link so only the latest setup link can be used. + + + ## Bug fixes From f8a5f1efda84855c02992f88f3a42c5fed1b44de Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 14:37:37 +1000 Subject: [PATCH 2/7] docs: sync recipient delivery API contracts --- openapi-app.json | 85 ++++++++++++++++++++++++++++++++++++++++++-- openapi-sending.json | 8 ++--- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/openapi-app.json b/openapi-app.json index 439773f..1a895ef 100644 --- a/openapi-app.json +++ b/openapi-app.json @@ -311,9 +311,37 @@ ], "type": "object" }, + "DeliveryLogDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/DeliveryLogItem" + }, + { + "additionalProperties": false, + "properties": { + "recipients": { + "items": { + "$ref": "#/components/schemas/DeliveryLogRecipient" + }, + "maxItems": 50, + "type": "array" + } + }, + "required": [ + "recipients" + ], + "type": "object" + } + ] + }, "DeliveryLogItem": { "additionalProperties": false, "properties": { + "accepted_recipient_count": { + "maximum": 50, + "minimum": 0, + "type": "integer" + }, "attempts": { "type": "number" }, @@ -353,6 +381,16 @@ "null" ] }, + "recipient_count": { + "maximum": 50, + "minimum": 0, + "type": "integer" + }, + "rejected_recipient_count": { + "maximum": 50, + "minimum": 0, + "type": "integer" + }, "sent_at": { "description": "ISO 8601 timestamp when email was sent", "type": [ @@ -415,7 +453,10 @@ "attempts", "sent_at", "size_bytes", - "created_at" + "created_at", + "recipient_count", + "accepted_recipient_count", + "rejected_recipient_count" ], "type": "object" }, @@ -456,7 +497,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/DeliveryLogItem" + "$ref": "#/components/schemas/DeliveryLogDetail" }, "meta": { "$ref": "#/components/schemas/ResponseMeta" @@ -470,6 +511,46 @@ ], "unevaluatedProperties": false }, + "DeliveryLogRecipient": { + "additionalProperties": false, + "properties": { + "email": { + "format": "email", + "type": "string" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "status": { + "enum": [ + "accepted", + "rejected" + ], + "type": "string" + }, + "type": { + "enum": [ + "to", + "cc", + "bcc" + ], + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "email", + "type", + "status", + "reason" + ], + "type": "object" + }, "DomainDeletedResponse": { "allOf": [ { diff --git a/openapi-sending.json b/openapi-sending.json index fa2138b..156ebcd 100644 --- a/openapi-sending.json +++ b/openapi-sending.json @@ -337,19 +337,19 @@ "type": "array" }, "bcc": { - "description": "BCC recipients (max 100)", + "description": "BCC recipients (subject to 50 total To, CC, and BCC recipients)", "items": { "$ref": "#/components/schemas/Recipient" }, - "maxItems": 100, + "maxItems": 49, "type": "array" }, "cc": { - "description": "CC recipients (max 100)", + "description": "CC recipients (subject to 50 total To, CC, and BCC recipients)", "items": { "$ref": "#/components/schemas/Recipient" }, - "maxItems": 100, + "maxItems": 49, "type": "array" }, "custom_headers": { From 20a0d050347fa1fc2cac8db4aab836ddb7df92f5 Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 14:54:59 +1000 Subject: [PATCH 3/7] docs: expose recipient delivery outcomes --- changelog.mdx | 2 ++ openapi-app.json | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/changelog.mdx b/changelog.mdx index 77fb495..0c0a943 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -9,6 +9,8 @@ rss: true Outgoing usage now counts each accepted To, CC, and BCC recipient. A message sent to several recipients is billed by recipient, while addresses rejected before acceptance are not charged. +Delivery log summaries show accepted and rejected recipient totals. Log details list each recipient outcome in sending order. + Blind-copy addresses remain part of delivery without being exposed in the delivered message headers. Repeated account setup requests reuse work already in progress. When a newer setup email is issued, its link replaces the earlier link so only the latest setup link can be used. diff --git a/openapi-app.json b/openapi-app.json index 1a895ef..776700a 100644 --- a/openapi-app.json +++ b/openapi-app.json @@ -320,6 +320,7 @@ "additionalProperties": false, "properties": { "recipients": { + "description": "Ordered recipient outcome snapshot for this send", "items": { "$ref": "#/components/schemas/DeliveryLogRecipient" }, @@ -338,6 +339,7 @@ "additionalProperties": false, "properties": { "accepted_recipient_count": { + "description": "Recipient occurrences accepted by the provider", "maximum": 50, "minimum": 0, "type": "integer" @@ -382,11 +384,13 @@ ] }, "recipient_count": { + "description": "Total recipient occurrences", "maximum": 50, "minimum": 0, "type": "integer" }, "rejected_recipient_count": { + "description": "Recipient occurrences rejected by the provider", "maximum": 50, "minimum": 0, "type": "integer" @@ -515,16 +519,19 @@ "additionalProperties": false, "properties": { "email": { + "description": "Recipient email address", "format": "email", "type": "string" }, "reason": { + "description": "Recipient-specific rejection reason when available", "type": [ "string", "null" ] }, "status": { + "description": "Provider acceptance outcome", "enum": [ "accepted", "rejected" @@ -532,6 +539,7 @@ "type": "string" }, "type": { + "description": "Recipient header type when known", "enum": [ "to", "cc", From ec1e16dac6032cd253abe7cf84b17ae3b8d3dea8 Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 16:52:07 +1000 Subject: [PATCH 4/7] docs: clarify accepted-recipient billing Document provider-accepted recipient billing and preserve unknown historical recipient outcomes as nullable in the Management API contract. --- guides/billing.mdx | 6 +- openapi-app.json | 183 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 163 insertions(+), 26 deletions(-) diff --git a/guides/billing.mdx b/guides/billing.mdx index a39b8be..6452e5d 100644 --- a/guides/billing.mdx +++ b/guides/billing.mdx @@ -20,10 +20,12 @@ Sendmux charges email usage from the team balance. | Activity | Rate | | --- | --- | -| Standard outgoing mail through your own providers | `$0.15 / 1,000 emails` | -| Outgoing mail through the managed Amazon SES route | `$0.25 / 1,000 emails` | +| Standard outgoing mail through your own providers | `$0.15 / 1,000 provider-accepted recipient occurrences` | +| Outgoing mail through the managed Amazon SES route | `$0.25 / 1,000 provider-accepted recipient occurrences` | | Accepted inbound mail | `$0.15 / 1,000 emails` | +For outgoing mail, each provider-accepted To, CC, or BCC recipient occurrence is billed. Addresses rejected before provider acceptance are not charged. + Inbound usage is counted when a message is accepted into a mailbox. Mail rejected by mailbox allow or block rules is not billed. Usage transactions can be grouped, so the transaction table may show one **Incoming email usage** line for several received messages in a short period. diff --git a/openapi-app.json b/openapi-app.json index 776700a..39d3d2c 100644 --- a/openapi-app.json +++ b/openapi-app.json @@ -312,37 +312,166 @@ "type": "object" }, "DeliveryLogDetail": { - "allOf": [ - { - "$ref": "#/components/schemas/DeliveryLogItem" + "additionalProperties": false, + "properties": { + "accepted_recipient_count": { + "description": "Recipient occurrences accepted by the provider, or null when no recipient snapshot exists", + "maximum": 50, + "minimum": 0, + "type": [ + "integer", + "null" + ] }, - { - "additionalProperties": false, - "properties": { - "recipients": { - "description": "Ordered recipient outcome snapshot for this send", - "items": { - "$ref": "#/components/schemas/DeliveryLogRecipient" - }, - "maxItems": 50, - "type": "array" - } + "attempts": { + "type": "number" + }, + "created_at": { + "description": "ISO 8601 creation timestamp", + "type": "string" + }, + "from_email": { + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Log public ID", + "example": "dlog_clxxxxxxxxxxxxxxxxxxxxxxxxx", + "type": "string" + }, + "message_id": { + "description": "Email Message-ID header", + "type": [ + "string", + "null" + ] + }, + "provider_id": { + "description": "Provider public ID", + "type": [ + "string", + "null" + ] + }, + "provider_name": { + "description": "Provider display name", + "type": [ + "string", + "null" + ] + }, + "recipient_count": { + "description": "Total recipient occurrences, or null when no recipient snapshot exists", + "maximum": 50, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "recipients": { + "description": "Ordered recipient outcome snapshot for this send, or null when no snapshot exists", + "items": { + "$ref": "#/components/schemas/DeliveryLogRecipient" }, - "required": [ - "recipients" + "maxItems": 50, + "type": [ + "array", + "null" + ] + }, + "rejected_recipient_count": { + "description": "Recipient occurrences rejected by the provider, or null when no recipient snapshot exists", + "maximum": 50, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "sent_at": { + "description": "ISO 8601 timestamp when email was sent", + "type": [ + "string", + "null" + ] + }, + "sent_from_email": { + "description": "The actual from address used for delivery after transformation. NULL if no transformation occurred or for older log entries.", + "type": [ + "string", + "null" + ] + }, + "size_bytes": { + "type": [ + "number", + "null" + ] + }, + "status": { + "enum": [ + "pending", + "sent", + "failed", + "rejected" ], - "type": "object" + "type": "string" + }, + "status_reason": { + "type": [ + "string", + "null" + ] + }, + "subject": { + "type": [ + "string", + "null" + ] + }, + "to_email": { + "type": [ + "string", + "null" + ] } - ] + }, + "required": [ + "id", + "provider_id", + "provider_name", + "message_id", + "from_email", + "sent_from_email", + "to_email", + "subject", + "status", + "status_reason", + "attempts", + "sent_at", + "size_bytes", + "created_at", + "recipient_count", + "accepted_recipient_count", + "rejected_recipient_count", + "recipients" + ], + "type": "object" }, "DeliveryLogItem": { "additionalProperties": false, "properties": { "accepted_recipient_count": { - "description": "Recipient occurrences accepted by the provider", + "description": "Recipient occurrences accepted by the provider, or null when no recipient snapshot exists", "maximum": 50, "minimum": 0, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "attempts": { "type": "number" @@ -384,16 +513,22 @@ ] }, "recipient_count": { - "description": "Total recipient occurrences", + "description": "Total recipient occurrences, or null when no recipient snapshot exists", "maximum": 50, "minimum": 0, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "rejected_recipient_count": { - "description": "Recipient occurrences rejected by the provider", + "description": "Recipient occurrences rejected by the provider, or null when no recipient snapshot exists", "maximum": 50, "minimum": 0, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "sent_at": { "description": "ISO 8601 timestamp when email was sent", From 5c3e6e3732e145c75982d7026e92c1c8f61d026c Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 18:24:11 +1000 Subject: [PATCH 5/7] docs: clarify duplicate-recipient billing --- changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.mdx b/changelog.mdx index 0c0a943..bceb43b 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -7,7 +7,7 @@ rss: true ## Bug fixes -Outgoing usage now counts each accepted To, CC, and BCC recipient. A message sent to several recipients is billed by recipient, while addresses rejected before acceptance are not charged. +Outgoing usage now counts every accepted To, CC, and BCC recipient occurrence. If the same address appears more than once, each accepted occurrence is billed; addresses rejected before acceptance are not charged. Delivery log summaries show accepted and rejected recipient totals. Log details list each recipient outcome in sending order. From 612aa7f3a590a0e3c2c9dbe645ba2336792a92c4 Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 18:26:03 +1000 Subject: [PATCH 6/7] docs: correct recipient billing summary --- changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.mdx b/changelog.mdx index bceb43b..17bdcf6 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -4,7 +4,7 @@ description: "New features, improvements, and fixes across the Sendmux platform. rss: true --- - + ## Bug fixes Outgoing usage now counts every accepted To, CC, and BCC recipient occurrence. If the same address appears more than once, each accepted occurrence is billed; addresses rejected before acceptance are not charged. From 1929f77e6390000fa1d61855681658fc81d52eb9 Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda Date: Tue, 28 Jul 2026 18:26:51 +1000 Subject: [PATCH 7/7] docs: explain duplicate recipient occurrences --- guides/billing.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guides/billing.mdx b/guides/billing.mdx index 6452e5d..5415bdc 100644 --- a/guides/billing.mdx +++ b/guides/billing.mdx @@ -26,6 +26,8 @@ Sendmux charges email usage from the team balance. For outgoing mail, each provider-accepted To, CC, or BCC recipient occurrence is billed. Addresses rejected before provider acceptance are not charged. +If the same address appears in more than one recipient field, each provider-accepted occurrence is billed separately. + Inbound usage is counted when a message is accepted into a mailbox. Mail rejected by mailbox allow or block rules is not billed. Usage transactions can be grouped, so the transaction table may show one **Incoming email usage** line for several received messages in a short period.