Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

### New Features

#### Outgoing MMS

- **Outgoing MMS support** — pass an `MmsMessage` (instead of a text string) to `Message` or `MessageBuilder` to send an MMS with an optional subject, body, and attachments:
```php
$mms = new MmsMessage('Subject', 'Body', [
new MmsAttachment('image/png', base64_encode($bytes), 'photo.png'),
]);
$message = (new MessageBuilder($mms, ['+15550100']))->build();
```
Attachments carry a MIME type, base64-encoded data, and an optional filename; null subject, text, and filename are omitted from the request body. MMS fields are encrypted when an `Encryptor` is set, and an MMS message is mutually exclusive with an SMS text body on the wire.

## [2.6.1] - 2026-09-23

### Bug Fixes

- **Message priority always serialized** — a message's `priority` is now always included in the JSON body (defaulting to `0` when unset), matching the wire format of the other official SDKs.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A modern PHP client for the [SMSGate](https://sms-gate.app) API: send SMS messag

## 📖 About

`capcom6/android-sms-gateway` is a type-safe PHP library for the SMSGate 3rd-party API. It covers messages (send, state, listing, cancellation), inbox refresh with individual or batch webhook delivery and attachment download, devices, webhooks, settings, logs, health checks, and the JWT token lifecycle, with a fluent `MessageBuilder` for message construction and an optional `Encryptor` for end-to-end encryption. Works with any PSR-18 HTTP client (Guzzle, curl, or others) and PHP 7.4+.
`capcom6/android-sms-gateway` is a type-safe PHP library for the SMSGate 3rd-party API. It covers messages (send SMS and MMS, state, listing, cancellation), inbox refresh with individual or batch webhook delivery and attachment download, devices, webhooks, settings, logs, health checks, and the JWT token lifecycle, with a fluent `MessageBuilder` for message construction and an optional `Encryptor` for end-to-end encryption. Works with any PSR-18 HTTP client (Guzzle, curl, or others) and PHP 7.4+.

## 📚 Table of Contents

Expand All @@ -33,7 +33,7 @@ A modern PHP client for the [SMSGate](https://sms-gate.app) API: send SMS messag
## ⭐ Features

- Fluent `MessageBuilder` for messages and `SettingsBuilder` for settings
- Messages: send, state, listing, and cancellation
- Messages: send SMS text or MMS (subject, body, attachments), state, listing, and cancellation
- PSR-18 HTTP client and PSR-17 factories, auto-discovered
- Basic and JWT authentication with token generation and revocation
- Inbox refresh with individual or batch webhook delivery and MMS attachment download
Expand Down
65 changes: 62 additions & 3 deletions src/Domain/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ class Message implements SerializableInterface {
* Message ID, will be generated automatically if not set
*/
private ?string $id;

/**
* Message text
* Long message will be divided into parts
*/
private string $message;
/**
* MMS message content (mutually exclusive with text/data messages)
* @var MmsMessage|null
*/
private ?MmsMessage $mmsMessage;

/**
* Time to live in seconds
* If message is not received by device in this time, it will be failed
Expand Down Expand Up @@ -50,10 +57,11 @@ class Message implements SerializableInterface {
private ?string $validUntil;

/**
* @param mixed $message string or MmsMessage
* @param array<string> $phoneNumbers
*/
public function __construct(
string $message,
$message,
Comment thread
capcom6 marked this conversation as resolved.
array $phoneNumbers,
?string $id = null,
?int $ttl = null,
Expand All @@ -62,19 +70,43 @@ public function __construct(
?int $priority = null,
?string $validUntil = null
) {
if (is_scalar($message)) {
$message = (string) $message;
}

if (!is_string($message) && !($message instanceof MmsMessage)) {
throw new \InvalidArgumentException('message must be a string or MmsMessage');
}
Comment thread
capcom6 marked this conversation as resolved.

if ($ttl !== null && $validUntil !== null) {
throw new \InvalidArgumentException('validUntil and ttl cannot be set at the same time');
}

$this->id = $id;
$this->message = $message;
$this->ttl = $ttl;
$this->simNumber = $simNumber;
$this->withDeliveryReport = $withDeliveryReport;
$this->phoneNumbers = $phoneNumbers;
$this->isEncrypted = false;
$this->priority = $priority;
$this->validUntil = $validUntil;

if ($message instanceof MmsMessage) {
$this->mmsMessage = $message;
Comment thread
capcom6 marked this conversation as resolved.
$this->message = '';
} else {
$this->message = $message;
$this->mmsMessage = null;
}
}


/**
* Get MMS message content (null if not set)
* @return MmsMessage|null
*/
public function MmsMessage(): ?MmsMessage {
return $this->mmsMessage;
}

public function Encrypt(Encryptor $encryptor): self {
Expand All @@ -88,19 +120,46 @@ public function Encrypt(Encryptor $encryptor): self {
fn(string $phoneNumber) => $encryptor->Encrypt($phoneNumber),
$this->phoneNumbers
);

if ($this->mmsMessage !== null) {
$this->mmsMessage = new MmsMessage(
$this->mmsMessage->Subject() !== null
? $encryptor->Encrypt($this->mmsMessage->Subject())
: null,
$this->mmsMessage->Text() !== null
? $encryptor->Encrypt($this->mmsMessage->Text())
: null,
array_map(
fn(MmsAttachment $attachment) => new MmsAttachment(
$attachment->ContentType(),
$encryptor->Encrypt($attachment->Data()),
$attachment->Name() !== null
? $encryptor->Encrypt($attachment->Name())
: null
),
$this->mmsMessage->Attachments()
)
);
}

return $this;
}

public function ToObject(): object {
$obj = (object) [
'id' => $this->id,
'message' => $this->message,
'simNumber' => $this->simNumber,
'withDeliveryReport' => $this->withDeliveryReport,
'isEncrypted' => $this->isEncrypted,
'phoneNumbers' => $this->phoneNumbers,
];

if ($this->mmsMessage !== null) {
$obj->mmsMessage = $this->mmsMessage->ToObject();
} else {
$obj->message = $this->message;
}

if ($this->priority !== null) {
$obj->priority = $this->priority;
}
Expand Down
12 changes: 6 additions & 6 deletions src/Domain/MessageBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class MessageBuilder {
private ?string $id = null;

/**
* @var string
* @var string|MmsMessage
*/
private string $message;
private $message;

/**
* @var int|null
Expand Down Expand Up @@ -50,10 +50,10 @@ class MessageBuilder {
private ?string $validUntil = null;

/**
* @param string $message
* @param string|MmsMessage $message
* @param array<string> $phoneNumbers
*/
public function __construct(string $message, array $phoneNumbers) {
public function __construct($message, array $phoneNumbers) {
$this->message = $message;
$this->phoneNumbers = $phoneNumbers;
}
Expand All @@ -72,10 +72,10 @@ public function setId(?string $id): self {
/**
* Set message text
*
* @param string $message
* @param string|MmsMessage $message
* @return $this
*/
public function setMessage(string $message): self {
public function setMessage($message): self {
$this->message = $message;
return $this;
}
Expand Down
63 changes: 63 additions & 0 deletions src/Domain/MmsAttachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace AndroidSmsGateway\Domain;

/**
* A single attachment of an MMS message.
*/
final class MmsAttachment {
/**
* MIME type of the attachment (e.g. image/png)
* @var string
*/
private string $contentType;

/**
* Base64-encoded attachment content
* @var string
*/
private string $data;

/**
* Optional file name of the attachment
* @var string|null
*/
private ?string $name;

/**
* @param string $contentType MIME type of the attachment (e.g. image/png)
* @param string $data Base64-encoded attachment content
* @param string|null $name Optional file name of the attachment
*/
public function __construct(string $contentType, string $data, ?string $name = null) {
$this->contentType = $contentType;
$this->data = $data;
$this->name = $name;
}

public function ContentType(): string {
return $this->contentType;
}

public function Data(): string {
return $this->data;
}

public function Name(): ?string {
return $this->name;
}

public function ToObject(): \stdClass {
$obj = (object) [
'contentType' => $this->contentType,
'name' => $this->name,
'data' => $this->data,
];

if ($this->name === null) {
unset($obj->name);
}

return $obj;
}
}
77 changes: 77 additions & 0 deletions src/Domain/MmsMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace AndroidSmsGateway\Domain;

/**
* An MMS message with optional subject, text and attachments.
*/
final class MmsMessage {
/**
* Optional subject of the MMS
* @var string|null
*/
private ?string $subject;

/**
* Optional text body of the MMS
* @var string|null
*/
private ?string $text;

/**
* List of attachments. Omitted entirely from the wire when empty.
* @var array<MmsAttachment>
*/
private array $attachments;

/**
* @param string|null $subject Optional subject of the MMS
* @param string|null $text Optional text body of the MMS
* @param array<MmsAttachment> $attachments List of attachments
*/
public function __construct(?string $subject = null, ?string $text = null, array $attachments = []) {
$this->subject = $subject;
$this->text = $text;
$this->attachments = array_values($attachments);
}

public function Subject(): ?string {
return $this->subject;
}

public function Text(): ?string {
return $this->text;
}

/**
* @return array<MmsAttachment>
*/
public function Attachments(): array {
return $this->attachments;
}

public function ToObject(): \stdClass {
$obj = (object) [
'subject' => $this->subject,
'text' => $this->text,
'attachments' => array_map(
static fn(MmsAttachment $attachment) => $attachment->ToObject(),
$this->attachments
),
];

if ($this->subject === null) {
unset($obj->subject);
}

if ($this->text === null) {
unset($obj->text);
}

if (count($this->attachments) === 0) {
unset($obj->attachments);
}

return $obj;
}
}
2 changes: 1 addition & 1 deletion tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public function testServer(): void {
);
$client = new Client($login, $password, Client::DEFAULT_URL, $curlClient);

$message = new Message(date('Y-m-d H:i:s'), [$phoneNumber]);
$message = new Message('Test message from `client-php`' . PHP_EOL . date('Y-m-d H:i:s'), [$phoneNumber]);

$messageState = $client->SendMessage($message);
$this->assertInstanceOf(MessageState::class, $messageState);
Expand Down
Loading
Loading