Appium: Capture Inbox Link for Email Based Login
Tags: appium, kotlin, testautomation, programming
Published: 2024-02-08 11:51:10

On this post I'll show you how to implement a solution to bypass an email based login in which a verification email is sent to your inbox and the deep link opens the app for you to continue the automated test.

I'll be using the following tools and services for the code snippets but a similar approach can be applied to other tech stack:

* Appium
* Kotlin
* Retrofit2
* Mailsac

First, we will have to go to [mailsac](https://mailsac.com/) and create an account. Don't worry about pricing, for this example, the free tier provides us with 1500 opts per months (the opts are equivalent to API calls, email inbox, etc), it is more than enough for a small to mid test automation project (assuming it has low rate of automated weekly iterations).

Once we complete sign up and we are logged in our account, go fill the input mail at the top left side of the page and click **Check the mail!** button.


![Check_mail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ix9x91qwnpnp6q2qfpss.png)

Once we validated the access the created inbox, go back to our dashboard and look for our API Key (that we will need to access the inbox via REST API). See the following steps:

- In the dashboard locate the **API Keys** section and click on "create API secret".

![api_secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/46zoymktruaqwrkfxyya.png)

- Then you will click on **Manage Keys**.

![manage_keys](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mp3prrg8qskbnsa2vqg1.png)

- From there add a name for your API Key (it can be anything you can identify) and save the API KEY since it won't show up again and we will need it later on.

![test_api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/htag1t9vu27hvdbk35m5.png)

### The Setup

With our mailsac account created and our API Key saved, let's start the implementation.

![ironman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqwto77newqsn0mzaj3b.gif)

At First is worth testing the API call to see that everything is working as expected. Open your terminal and add the following command (replace api-key and your-mail-inbox with your info):

```bash
curl -H 'Mailsac-Key: <api_key>' https://mailsac.com/api/addresses/<your-mail-inbox>/messages
```

The response should be empty list "[]" since we haven't received, any emails yet, but you can test on your own by sending a mail to the mailbox. Here's an example of what the response looks like with at least one mail in the inbox (it won't be formatted when you see it but for the sake of this blog post I'll make it look pretty):

```json

{

   "_id":"59w9HezR_PinWSOHl09yeoa",

   "from":[

      {

         "address":"test.test@test.com",

         "name":"Test User"

      }

   ],

   "to":[

      {

         "address":"test@mailsac.com",

         "name":"Tester"

      }

   ],

   "cc":[

      

   ],

   "bcc":[

      

   ],

   "subject":"test mail",

   "savedBy":null,

   "inbox":"test@mailsac.com",

   "originalInbox":"test@mailsac.com",

   "domain":"mailsac.com",

   "received":"2023-02-05T22:27:06.266Z",

   "size":3070,

   "attachments":[

      

   ],

   "ip":"",

   "via":"",

   "folder":"inbox",

   "labels":[

      

   ],

   "read":null,

   "rtls":true,

   "links":[

      "https://www.google.com/"

   ],

   "spam":0

}

```
As you can see, we get a LOT of information from our mail, and from it, we see a "key" called "links", that is what we are targeting to get from our API request.

Now let's jump to the code to  implement our solution:

- We will need to setup our HTTP interpreter with Retrofit2. For that we will need the classes below (I'll explain the code from top to bottom):

```kotlin
class ServiceGenerator {

    private val baseURL = "https://mailsac.com/api/addresses/"
    private val AuthKey = System.getenv("MAILSAC_ACCESS_KEY")
    private val Interceptor = EmailAuthenticationInterceptor(AuthKey)
    private val emailHttpClient = OkHttpClient.Builder().addInterceptor(Interceptor)

    private val builderEmail: Retrofit.Builder = Retrofit.Builder()
           .baseUrl(URL)
           .addConverterFactory(GsonConverterFactory.create())

    private var retrofit: Retrofit = builder.build()

    fun <S> createServiceEmail(serviceClass: Class<S>): S {
        retrofit = builderEmail.client(emailHttpClient.build()).build()
        return retrofit.create(serviceClass)
    }
}
```

1. Create `serviceGenerator` class that will contain our key and request interceptor usage.
2. Initialize AuthKey variable that will contain our mailsac API key. (Here I'm calling an environment variable for security reasons but you can save it on a file, a string or Json file. Whatever works for you).
3. Initialize our interceptor variable that contains the Object of a class we will see later on. Pass the AuthKey as argument.
4. Initialize your http interpreter.
5. Setup your Retrofit builder (I explained it in a previous blog, the retrofit will manage your request in a lower level)
6. Create your service method (This is what we will handle and interpret the requests)

```kotlin
import java.io.IOException
import okhttp3.Interceptor
import okhttp3.Response


class EmailAuthenticationInterceptor(private val authToken: String) : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val original = chain.request()
        val builderWithAuth = original.newBuilder()
            .header("Mailsac-Key", authToken)
            .build()
        return chain.proceed(builderWithAuth)
    }
}
```

1. Create the `EmailAuthenticationInterceptor` class that receives a string as a parameter (this is expecting the API key).
2. Create override method `intercept` from which we manage the headers we will be passing in our request, in this case the API key with the key "Mailsac-Key".

```kotlin
import okhttp3.MultipartBody
import retrofit2.Call
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.http.*
import java.io.File

interface MessageService {

    @GET("<user-email>/messages")
    fun getEmails(): Call<ResponseBody>
}
```

1. Setup interface that handles the REST API methods.
2. GET method annotation then receives the endpoint part with the your email inbox created in mailsac.
3. Setup method type Call ResponseBody .

Finally we have everything we need to use our solution.

This final stage will always depend on the usage that you need, but for our case scenario we will catch the link we need to verify the login.

At this point, I'll assumed you've already setup your steps to have the mail sent to your inbox , so in order to catch the info we need here is one approach you can use:

```kotlin
val service = ServiceGenerator().createServiceEmail(MessageService::class.java)
val response: Response<ResponseBody> = service.getEmails().execute()
val responseBody: String = response.body()!!.string()

val responseJsonArray = JsonParser.parseString(responseBody).asJsonArray
        
for(jsonElement in responseJsonArray){
           val jsonObject = jsonElement.asJsonObject
           val appUrl = jsonObject.get("links").asString
                openLink(driver, appUrl)
                break
            
        }
```

As you can see, initializing the service is straight forward:

1. We call our service by passing the MessageService
2. Get the response by calling our "getMails()" method and executing the request.
3. Parse the Json as a Jsonarray 
4. Finally iterate trough it until you get the link you need.
5. The openLink() method is just a wrapped "drive.get()" method.

This is the end of the walkthrough. I wanted to share this solution with anyone looking for a way to automate a similar process. There are many other ways to do it, but this one in particular is suited for a local and test environments using CI tools if you like, since you won't need a user interaction  of any kind, everything is handle via API, you won't need to update a token or have the need to add extra steps for an exception, ([Gmail API](https://developers.google.com/gmail/api/quickstart/java) for example has this problem).

Hopefully you find this solution walktrough useful and if you have any feedback or know any libraries that can facilitate a similar progress, please share it in the comments below.