This guide is intended for people wishing to implement the BulkSMS API client library in an unsupported language. If you simply want to use an existing client implementation of the BulkSMS API then you should read the BulkSMS API User Guide for your choice of language.
The library structure is designed to be simple to use for the typical use cases of the majority of BulkSMS users. The structure is the same, or as close as possible within language limitations, regardless of the language in use. This includes the names used for methods and the structure of arguments and returned values. While this may not be idiomatic in any given language, it helps maintain consistency in documentation and assists in support handling.
The exposed end-user API is expected to change and grow over time, adding useful features and fixing issues discovered through actual use. Equally, the underlying HTTP based service against which the client library operates is expected to change to meet these demands. It is expected that a given version of any implementation will operate against a fixed version of the underlying HTTP service. Changes to the HTTP service which can be made without breaking the library implementations may occur within a given revision of the service, otherwise a new version of the service will need to be published. Consequently, a new version of the library implementation will need to be published to take advantage of any changes to the HTTP service.
The expectation is that the vast majority of the library implementations will be object orientated and as such we’ve structured certain types to maintain consistency between implementations. These are detailed below in pseudo-code which should be understandable by most developers.
Where the language has packaging conventions to allow separation of different modules, the preference is to put the library in a package called “BulkSMS.SMS”, where the period is a notional hierarchical symbol. Examples of existing package naming include
| Language | Package | Comment |
|---|---|---|
| Perl | BulkSMS::SMS | Perl has notional hierarchy with “::” |
| PHP | BulkSMS_SMS | PHP has no packaging but convention using “_” |
| Java | com.bulksms.sms | Java has strong packaging conventions |
The HTTP service makes use of HTTP Basic auth, which can operate in a challenge-response mode. In such a case an unauthenticated request would receive a 401 response from the server. The expectation would then be that the client makes a further request but now including the authentication header. This is clearly wasteful of resources. A well behaved library implementation will ensure that a suitably created authentication header is included in all requests, preemptively, without expecting or requiring a 401 response.
When making HTTP requests you should identify the library implementation in the User-Agent header. The preferred format is BulkSMS.com/<language>lib-<version>, where the perl library, at version 1.0.2 would identify itself as BulkSMS.com/perllib-1.0.2.
There are many properties of the JSON request entities which are optional. An example would be the “from” flag of a MessageSubmission. Where a user has not explicitly provided a value for an optional request property, you MUST NOT provide a value in the request. Many of these optional properties have user specific settings which the HTTP service will use when no value has been provided. Overriding this functionality of the HTTP service would result in differences in operation between library implementations.
Errors, as presented to the end-user, are handled through exceptions. The aim is to enforce correct error handling in client code while making it easy to write understandable code for the normal, non-error cases.
enum Ternary { YES, NO, UNSPECIFIED }
enum StatusReportingMode { ALL, ERRORS, NONE, UNSPECIFIED }
enum MessageBodyEncoding { TEXT, UNICODE, BINARY }
enum ProtocolId {
IMPLICIT,
SHORT_MESSAGE_TYPE_0,
REPLACE_MESSAGE_TYPE_1,
REPLACE_MESSAGE_TYPE_2,
REPLACE_MESSAGE_TYPE_3,
REPLACE_MESSAGE_TYPE_4,
REPLACE_MESSAGE_TYPE_5,
REPLACE_MESSAGE_TYPE_6,
REPLACE_MESSAGE_TYPE_7,
RETURN_CALL,
ME_DATA_DOWNLOAD,
ME_DEPERSONALIZE,
SIM_DATA_DOWNLOAD
}
enum MessageClass { CLASS_0_FLASH, CLASS_1_ME, CLASS_2_SIM, CLASS_3_TE }
interface ContactGroup {
Integer getId()
String getName()
Integer getSize()
}
interface Contact {
String getForenames()
String getSurname()
String getMsisdn()
Integer[] getGroupIds()
Integer getTimestamp()
}
enum {
DONT_NORMALIZE_MSISDNS,
DONT_REPLACE_EXISTING
}
interface UserProfile {
Integer getUserId()
String getUsername()
Decimal getCredits()
String getHonorific()
String getForenames()
String getSurname()
String getEmail()
String getMsisdn()
String getCountry()
String getCompany()
String getTimezone()
Integer getDailyQuota()
Integer getDailyQuotaUsed()
String getJsonApiToken()
String getMessageRelayUrl()
String getStatusReportRelayUrl()
Integer getLowCreditNotificationLevel()
String getDefaultOriginAddress()
String[] getOriginAddresses()
UserProfileFlag[] getFlags()
}
enum UserProfileFlag {
HAS_FULL_ORIGIN_ADDRESS_CONTROL,
CAN_TRANSFER_CREDITS,
LOW_CREDIT_NOTIFY_EMAIL,
LOW_CREDIT_NOTIFY_SMS
}
interface MessageSubmission {
String uniqueId
MessageSubmissionScheduling scheduling
MessageSubmissionFlag[] flags
}
interface MessageSubmissionScheduling {
Iso8601 date
String description
}
interface MessageSubmission {
String sender
String[] recipients
Integer[] groups,
String body,
MessageBodyEncoding encoding,
Ternary repliable,
Integer routingProfile,
Integer maxConcatParts,
String clientId,
Integer protocolId,
MessageClass messageClass,
StatusReportingMode statusReporting,
MessageSubmissionTemplateDefaults templateDefaults
}
In the JSON API you can specify origin and destination addresses as either a simple string, or an object like { address:<string>, type:<string> }. While the simple string method is sufficient, there are some benefits to supporting the complete version. One such benefit is making it simple to allow a user to only specify either an origin address or a repliable message.
The allow use of these address types, simply change the sender and recipients properties of the MessageSubmission to the suitable address type, and remove the repliable property. Also suitable methods should be added to the MessageSubmissionBuilder.
interface OriginAddress {
constant OriginAddress REPLIABLE = OriginAddress(address = "*", type = OriginAddressType.INTERNATIONAL)
OriginAddressType type
String address
}
enum OriginAddressType { INTERNATIONAL, NATIONAL, SHORTCODE, ALPHANUMERIC, UNSPECIFIED }
interface DestinationAddress {
DestinationAddressType type
String address
}
enum DestinationAddressType { INTERNATIONAL, UNSPECIFIED }
enum MessageSubmissionFlag {
ALLOW_DUPLICATE_RECIPIENTS,
ALLOW_SCHEDULING_IN_PAST,
ALLOW_INVALID_MSISDNS,
REPLACE_INVALID_CHARACTERS,
AUTO_CONCAT,
AUTO_UNICODE
}
interface MessageSubmissionTemplateDefaults {
String h,
String fn,
String sn
}
interface MessageSubmissionResult {
MessageSubmissionStatus getStatus(),
Integer getSubmissionId(),
String getMessage()
}
enum MessageSubmissionStatus { IN_PROGRESS, SCHEDULED }
interface Message {
Integer getId()
String getFrom()
String getTo()
Date getCreatedDate()
String getBody()
MessageBodyEncoding getEncoding()
String getReferringClientId()
}
interface MessageState {
String getStatus(),
Boolean isFinal(),
String getMsisdn(),
String getClientId()
}
interface BulkSMSClient {
setDefaultSubmissionFlags(MessageSubmissionFlag[] flags),
MessageSubmissionResult sendMessage(String msisdn, String body),
MessageSubmissionResult sendMessage(MessageSubmission submission),
MessageSubmissionResult sendMessage(MessageSubmission[] submissions),
MessageSubmissionResult sendMessage(MessageSubmission submission, MessageSubmissionOptions options),
MessageSubmissionResult sendMessage(MessageSubmission[] submissions, MessageSubmissionOptions options),
Decimal estimateMessage(String msisdn, String body),
Decimal estimateMessage(MessageSubmission submission),
Decimal estimateMessage(MessageSubmission[] submissions),
Decimal estimateMessage(MessageSubmission submission, MessageSubmissionOptions options),
Decimal estimateMessage(MessageSubmission[] submissions, MessageSubmissionOptions options),
MessageState[] getMessageState(Integer submissionId, String msisdn = null, String clientId = null),
Message[] getInbox(Integer lastRetrievedId = 0)
ContactGroup[] getContactGroups(),
Contact[] updateContacts(Contact[] contacts, ContactUpdateFlag[] flags),
Contact[] getContacts(String filter = null, Integer offset = 0, Integer limit = 0),
UserProfile getUserProfile()
}
interface MessageSubmissionBuilder {
MessageSubmissionBuilder from(String from),
MessageSubmissionBuilder to(String to),
MessageSubmissionBuilder toGroup(Integer groupId),
MessageSubmissionBuilder body(String body),
MessageSubmissionBuilder body(String body, MessageBodyEncoding encoding),
MessageSubmissionBuilder routingProfile(Integer routingProfile),
MessageSubmissionBuilder maxConcatParts(Integer maxConcatParts),
MessageSubmissionBuilder clientId(String clientId),
MessageSubmissionBuilder protocolId(ProtocolId protocolId),
MessageSubmissionBuilder protocolId(Integer protocolId),
MessageSubmissionBuilder messageClass(MessageClass messageClass),
MessageSubmissionBuilder statusReportingMode(StatusReportingMode statusReportingMode),
MessageSubmission build()
}