
Offers a comprehensive client for mail.tm API, enabling complete API coverage, smart error handling, authentication, helper functions, and testing support for managing temporary email accounts and messages.
Status: Production Ready — Complete implementation with comprehensive error handling and all API endpoints.
A complete Kotlin Multiplatform (KMP) client for the mail.tm API, built on Ktor 3.x and kotlinx.serialization.
MockEngine for unit tests// settings.gradle.kts
repositories {
google()
mavenCentral()
}
// build.gradle.kts
dependencies {
implementation("io.github.hasanyalmanbas:mail-tm-client:1.0.6")
}// settings.gradle
repositories {
google()
mavenCentral()
}
// build.gradle
dependencies {
implementation 'io.github.hasanyalmanbas:mail-tm-client:1.0.6'
}import tm.mail.api.createMailTmClient
import tm.mail.api.ApiClient
import tm.mail.api.mailTmEngine
suspend fun demo() {
// Simple way - use convenience function
val client = createMailTmClient()
// Or create manually with platform engine
val manualClient = ApiClient(mailTmEngine())
// Create account and authenticate in one step
val authenticatedClient = ApiClient.createAccountAndAuthenticate(
engine = mailTmEngine(),
address = "user@example.com",
password = "secure-password"
)
// Or authenticate with existing account
val existingClient = ApiClient.authenticateExisting(
engine = mailTmEngine(),
address = "user@example.com",
password = "secure-password"
)
// Get all messages
val messages = authenticatedClient.getAllMessages()
println("You have ${messages.size} messages")
}val client = ApiClient(mailTmEngine())
// Get a random available domain and create account
val randomAccount = client.createRandomAccount("my-password")
println("Created account: ${randomAccount.address}")
// Authenticate and start using
val token = client.createToken(randomAccount.address, "my-password")
client.setToken(token.token)// Get unread messages only
val unreadMessages = client.getUnreadMessages()
// Get specific message details
val messageDetail = client.getMessageById("msg-id")
// Mark message as read
client.markMessageAsSeen("msg-id")
// Mark all messages as read
client.markAllMessagesAsSeen()
// Delete all messages
client.deleteAllMessages()
// Get message source
val source = client.getMessageSource("msg-id")try {
val account = client.createAccount("test@example.com", "password")
} catch (e: MailTmException.AccountAlreadyExists) {
println("Account already exists: ${e.message}")
// Access original API response
println("API Error: ${e.originalResponse?.error}")
} catch (e: MailTmException.InvalidDomain) {
println("Invalid domain: ${e.message}")
} catch (e: MailTmException.RateLimited) {
println("Rate limited, try again later")
} catch (e: MailTmException.NetworkError) {
println("Network error: ${e.message}")
// Access underlying cause
e.cause?.printStackTrace()
}// Advanced client configuration
val client = ApiClient.builder()
.baseUrl("https://api.mail.tm")
.enableLogging(true)
.requestTimeout(45_000L)
.maxRetries(5)
.build(engine)
// With custom configuration object
val config = ApiClientConfig(
enableLogging = true,
requestTimeoutMillis = 60_000L,
maxRetries = 3
)
val client = ApiClient.create(engine, config)// Check rate limits after API calls
client.getMessages()
val rateInfo = client.getLastRateLimitInfo()
rateInfo?.let { info ->
println("Remaining requests: ${info.remaining}/${info.limit}")
println("Reset time: ${info.reset}")
}// Setup SSE for real-time message notifications
val account = client.getMe()
val sseConfig = client.createSSEConfig(account.id)
// Use sseConfig.mercureUrl to connect to Server-Sent Events
// Platform-specific EventSource implementation requiredval message = client.getMessageById("message-id")
// Security verification information
message.verifications?.let { verifications ->
println("TLS: ${verifications.tls?.version}")
println("SPF Passed: ${verifications.spf}")
println("DKIM Passed: ${verifications.dkim}")
}
// JSON-LD context information
println("Context: ${message.context}")
println("Type: ${message.jsonLdType}")POST /token → createToken(address, password) - Get auth tokensetToken(token) - Set bearer token for requestsPOST /accounts → createAccount(address, password) - Create new accountGET /accounts/{id} → getAccountById(id) - Get account detailsDELETE /accounts/{id} → deleteAccount(id) - Delete accountGET /me → getMe() - Get current account infoGET /domains → getDomains(page?) - List available domainsGET /domains/{id} → getDomainById(id) - Get domain detailsGET /messages → getMessages(page?) - List messagesGET /messages/{id} → getMessageById(id) - Get message detailsDELETE /messages/{id} → deleteMessage(id) - Delete messagePATCH /messages/{id} → markMessageAsSeen(id, seen) - Mark as read/unreadGET /sources/{id} → getMessageSource(id) - Get raw message sourcecreateAccountAndAuthenticate() - Create account and login in one stepauthenticateExisting() - Login with existing credentialsgetRandomAvailableDomain() - Get a random active domaincreateRandomAccount() - Create account with random usernamegetAllMessages() - Get all messages (handles pagination)getUnreadMessages() - Get only unread messagesmarkAllMessagesAsSeen() - Mark all messages as readdeleteAllMessages() - Delete all messagesThe client provides specific exception types for different error scenarios:
MailTmException.BadRequest - 400 Bad RequestMailTmException.Unauthorized - 401 UnauthorizedMailTmException.NotFound - 404 Not FoundMailTmException.Conflict - 409 ConflictMailTmException.UnprocessableEntity - 422 Validation ErrorMailTmException.RateLimited - 429 Too Many RequestsMailTmException.Server - 5xx Server ErrorsMailTmException.AccountAlreadyExists - Email already registeredMailTmException.InvalidCredentials - Wrong email/passwordMailTmException.InvalidDomain - Domain not validMailTmException.AccountDisabled - Account is disabledMailTmException.MessageNotFound - Message doesn't existMailTmException.DomainNotAvailable - Domain not availableMailTmException.QuotaExceeded - Storage quota exceededMailTmException.NetworkError - Connection/network issuesMailTmException.TimeoutError - Request timeoutAll exceptions include the original API error response when available:
catch (e: MailTmException) {
println("Error: ${e.message}")
e.originalResponse?.let { response ->
println("API Error: ${response.error}")
println("Violations: ${response.violations}")
}
}The client is fully mockable using Ktor's MockEngine:
@Test
fun testCreateAccount() = runBlocking {
val mockEngine = MockEngine { request ->
respond(
content = ByteReadChannel("""{"id":"123","address":"test@example.com"}"""),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
val client = ApiClient.create(mockEngine)
val account = client.createAccount("test@example.com", "password")
assertEquals("123", account.id)
assertEquals("test@example.com", account.address)
}git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Production Ready: This client provides complete mail.tm API coverage with robust error handling and convenient helper functions.
Status: Production Ready — Complete implementation with comprehensive error handling and all API endpoints.
A complete Kotlin Multiplatform (KMP) client for the mail.tm API, built on Ktor 3.x and kotlinx.serialization.
MockEngine for unit tests// settings.gradle.kts
repositories {
google()
mavenCentral()
}
// build.gradle.kts
dependencies {
implementation("io.github.hasanyalmanbas:mail-tm-client:1.0.6")
}// settings.gradle
repositories {
google()
mavenCentral()
}
// build.gradle
dependencies {
implementation 'io.github.hasanyalmanbas:mail-tm-client:1.0.6'
}import tm.mail.api.createMailTmClient
import tm.mail.api.ApiClient
import tm.mail.api.mailTmEngine
suspend fun demo() {
// Simple way - use convenience function
val client = createMailTmClient()
// Or create manually with platform engine
val manualClient = ApiClient(mailTmEngine())
// Create account and authenticate in one step
val authenticatedClient = ApiClient.createAccountAndAuthenticate(
engine = mailTmEngine(),
address = "user@example.com",
password = "secure-password"
)
// Or authenticate with existing account
val existingClient = ApiClient.authenticateExisting(
engine = mailTmEngine(),
address = "user@example.com",
password = "secure-password"
)
// Get all messages
val messages = authenticatedClient.getAllMessages()
println("You have ${messages.size} messages")
}val client = ApiClient(mailTmEngine())
// Get a random available domain and create account
val randomAccount = client.createRandomAccount("my-password")
println("Created account: ${randomAccount.address}")
// Authenticate and start using
val token = client.createToken(randomAccount.address, "my-password")
client.setToken(token.token)// Get unread messages only
val unreadMessages = client.getUnreadMessages()
// Get specific message details
val messageDetail = client.getMessageById("msg-id")
// Mark message as read
client.markMessageAsSeen("msg-id")
// Mark all messages as read
client.markAllMessagesAsSeen()
// Delete all messages
client.deleteAllMessages()
// Get message source
val source = client.getMessageSource("msg-id")try {
val account = client.createAccount("test@example.com", "password")
} catch (e: MailTmException.AccountAlreadyExists) {
println("Account already exists: ${e.message}")
// Access original API response
println("API Error: ${e.originalResponse?.error}")
} catch (e: MailTmException.InvalidDomain) {
println("Invalid domain: ${e.message}")
} catch (e: MailTmException.RateLimited) {
println("Rate limited, try again later")
} catch (e: MailTmException.NetworkError) {
println("Network error: ${e.message}")
// Access underlying cause
e.cause?.printStackTrace()
}// Advanced client configuration
val client = ApiClient.builder()
.baseUrl("https://api.mail.tm")
.enableLogging(true)
.requestTimeout(45_000L)
.maxRetries(5)
.build(engine)
// With custom configuration object
val config = ApiClientConfig(
enableLogging = true,
requestTimeoutMillis = 60_000L,
maxRetries = 3
)
val client = ApiClient.create(engine, config)// Check rate limits after API calls
client.getMessages()
val rateInfo = client.getLastRateLimitInfo()
rateInfo?.let { info ->
println("Remaining requests: ${info.remaining}/${info.limit}")
println("Reset time: ${info.reset}")
}// Setup SSE for real-time message notifications
val account = client.getMe()
val sseConfig = client.createSSEConfig(account.id)
// Use sseConfig.mercureUrl to connect to Server-Sent Events
// Platform-specific EventSource implementation requiredval message = client.getMessageById("message-id")
// Security verification information
message.verifications?.let { verifications ->
println("TLS: ${verifications.tls?.version}")
println("SPF Passed: ${verifications.spf}")
println("DKIM Passed: ${verifications.dkim}")
}
// JSON-LD context information
println("Context: ${message.context}")
println("Type: ${message.jsonLdType}")POST /token → createToken(address, password) - Get auth tokensetToken(token) - Set bearer token for requestsPOST /accounts → createAccount(address, password) - Create new accountGET /accounts/{id} → getAccountById(id) - Get account detailsDELETE /accounts/{id} → deleteAccount(id) - Delete accountGET /me → getMe() - Get current account infoGET /domains → getDomains(page?) - List available domainsGET /domains/{id} → getDomainById(id) - Get domain detailsGET /messages → getMessages(page?) - List messagesGET /messages/{id} → getMessageById(id) - Get message detailsDELETE /messages/{id} → deleteMessage(id) - Delete messagePATCH /messages/{id} → markMessageAsSeen(id, seen) - Mark as read/unreadGET /sources/{id} → getMessageSource(id) - Get raw message sourcecreateAccountAndAuthenticate() - Create account and login in one stepauthenticateExisting() - Login with existing credentialsgetRandomAvailableDomain() - Get a random active domaincreateRandomAccount() - Create account with random usernamegetAllMessages() - Get all messages (handles pagination)getUnreadMessages() - Get only unread messagesmarkAllMessagesAsSeen() - Mark all messages as readdeleteAllMessages() - Delete all messagesThe client provides specific exception types for different error scenarios:
MailTmException.BadRequest - 400 Bad RequestMailTmException.Unauthorized - 401 UnauthorizedMailTmException.NotFound - 404 Not FoundMailTmException.Conflict - 409 ConflictMailTmException.UnprocessableEntity - 422 Validation ErrorMailTmException.RateLimited - 429 Too Many RequestsMailTmException.Server - 5xx Server ErrorsMailTmException.AccountAlreadyExists - Email already registeredMailTmException.InvalidCredentials - Wrong email/passwordMailTmException.InvalidDomain - Domain not validMailTmException.AccountDisabled - Account is disabledMailTmException.MessageNotFound - Message doesn't existMailTmException.DomainNotAvailable - Domain not availableMailTmException.QuotaExceeded - Storage quota exceededMailTmException.NetworkError - Connection/network issuesMailTmException.TimeoutError - Request timeoutAll exceptions include the original API error response when available:
catch (e: MailTmException) {
println("Error: ${e.message}")
e.originalResponse?.let { response ->
println("API Error: ${response.error}")
println("Violations: ${response.violations}")
}
}The client is fully mockable using Ktor's MockEngine:
@Test
fun testCreateAccount() = runBlocking {
val mockEngine = MockEngine { request ->
respond(
content = ByteReadChannel("""{"id":"123","address":"test@example.com"}"""),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
val client = ApiClient.create(mockEngine)
val account = client.createAccount("test@example.com", "password")
assertEquals("123", account.id)
assertEquals("test@example.com", account.address)
}git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Production Ready: This client provides complete mail.tm API coverage with robust error handling and convenient helper functions.