diff --git a/changelog.mdx b/changelog.mdx index 0b443f9..17bdcf6 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -4,6 +4,19 @@ 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. + +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. + + + ## Bug fixes diff --git a/guides/billing.mdx b/guides/billing.mdx index a39b8be..5415bdc 100644 --- a/guides/billing.mdx +++ b/guides/billing.mdx @@ -20,10 +20,14 @@ 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. + +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. diff --git a/openapi-app.json b/openapi-app.json index 439773f..39d3d2c 100644 --- a/openapi-app.json +++ b/openapi-app.json @@ -311,9 +311,168 @@ ], "type": "object" }, + "DeliveryLogDetail": { + "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" + ] + }, + "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" + }, + "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": "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, or null when no recipient snapshot exists", + "maximum": 50, + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, "attempts": { "type": "number" }, @@ -353,6 +512,24 @@ "null" ] }, + "recipient_count": { + "description": "Total recipient occurrences, or null when no recipient snapshot exists", + "maximum": 50, + "minimum": 0, + "type": [ + "integer", + "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": [ @@ -415,7 +592,10 @@ "attempts", "sent_at", "size_bytes", - "created_at" + "created_at", + "recipient_count", + "accepted_recipient_count", + "rejected_recipient_count" ], "type": "object" }, @@ -456,7 +636,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/DeliveryLogItem" + "$ref": "#/components/schemas/DeliveryLogDetail" }, "meta": { "$ref": "#/components/schemas/ResponseMeta" @@ -470,6 +650,50 @@ ], "unevaluatedProperties": false }, + "DeliveryLogRecipient": { + "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" + ], + "type": "string" + }, + "type": { + "description": "Recipient header type when known", + "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": {