takaya030の備忘録

PHP、Laravel、Docker などの話がメインです

Laravel で Tumblr API の Access Token を取得

Laravel で Tumblr API のアクセストークンを取得するプログラムを動かしたときのメモ

検証環境

$ php --version
PHP 7.1.4 (cli) (built: May 11 2017 17:22:31) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

$ php artisan --version
Laravel Framework 5.4.33

Tumblr API の認証についての注意点

  • 認証プロトコルは OAuth 1.0a
  • Twitter などと違い、開発コンソール画面で Access Token の確認ができないため、Web アプリケーションなどで Access Token が必要な場合は認証プログラムを使って取得する必要がある

OAuth パッケージについて

今回は Laravel5 対応の以下のパッケージを使用します
github.com

oriceon/oauth-5-laravel のインストール

composer.jsonrequire セクションに下記内容を追加

    "require": {
        "oriceon/oauth-5-laravel": "dev-master"
    }

その後 composer update でインストール

$ composer update

config/app.php を下記のように変更してパッケージを登録

--- config/app.php.orig Fri Aug 18 12:25:23 2017
+++ config/app.php      Wed Aug 16 16:55:38 2017
@@ -177,6 +177,11 @@
         App\Providers\EventServiceProvider::class,
         App\Providers\RouteServiceProvider::class,

+        /*
+         * OAuth
+         */
+        Artdarek\OAuth\OAuthServiceProvider::class,
+
     ],

     /*
@@ -212,6 +217,7 @@
         'Log' => Illuminate\Support\Facades\Log::class,
         'Mail' => Illuminate\Support\Facades\Mail::class,
         'Notification' => Illuminate\Support\Facades\Notification::class,
+        'OAuth'     => Artdarek\OAuth\Facade\OAuth::class,
         'Password' => Illuminate\Support\Facades\Password::class,
         'Queue' => Illuminate\Support\Facades\Queue::class,
         'Redirect' => Illuminate\Support\Facades\Redirect::class,

以下の内容で config/oauth-5-laravel.php を作成

<?php

return [

	/*
	|--------------------------------------------------------------------------
	| oAuth Config
	|--------------------------------------------------------------------------
	*/

	/**
	 * Storage
	 */
	'storage' => '\\OAuth\\Common\\Storage\\Session',

	/**
	 * Consumers
	 */
	'consumers' => [

		'Tumblr' => [
			'client_id'     => env('TUMBLR_CLIENT_ID'),
			'client_secret' => env('TUMBLR_CLIENT_SECRET'),
			// No scope - oauth1 doesn't need scope
		],

	]

];

.env に Consumer Key, Consumer Secret を追加

TUMBLR_CLIENT_ID=Your-Consumer-Key
TUMBLR_CLIENT_SECRET=Your-Consumer-Secret

OAuth 認証プログラム

app/Http/Controllers/TumblrAuthController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TumblrAuthController extends Controller
{
	public function loginWithTumblr(Request $request)
	{
		// get data from request
		$token  = $request->get('oauth_token');
		$verify = $request->get('oauth_verifier');
		
		// get tumblr service
		$tmb = \OAuth::consumer('Tumblr');
		
		// check if code is valid
		
		// if code is provided get user data and sign in
		if ( ! is_null($token) && ! is_null($verify))
		{
			// This was a callback request from tumblr, get the token
			$token = $tmb->requestAccessToken($token, $verify);
			
			//Var_dump
			//display whole array.
			dd($token);
		}
		// if not ask for permission first
		else
		{
			// get request token
			$reqToken = $tmb->requestRequestToken();
			
			// get Authorization Uri sending the request token
			$url = $tmb->getAuthorizationUri(['oauth_token' => $reqToken->getRequestToken()]);

			// return to tumblr login url
			return redirect((string)$url);
		}
	}
}

routes/web.php

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/tumblr', 'TumblrAuthController@loginWithTumblr');

OAuth 認証プログラムの実行

以下のコマンドで開発用サーバーが起動します

$ php artisan serve

web ブラウザで http://127.0.0.1:8000/tumblr にアクセスします
途中、以下の画面が表示されたときは Allow をクリックします
f:id:takaya030:20170818161156p:plain

web ブラウザに以下のような Access Token の表示が出れば成功です

StdOAuth1Token {#175 ▼
  #requestToken: "xxxxx...."
  #requestTokenSecret: "xxxxx..."
  #accessTokenSecret: "xxxxx..."
  #accessToken: "xxxxx..."
  #refreshToken: null
  #endOfLife: -9002
  #extraParams: []
}

glide で GAE/Go プロジェクトのパッケージ管理

Google App Engine for Go (GAE/Go) の開発環境に glide でパッケージをインストールする手順メモ

検証環境

Windows10 Home Edition
VirtualBox 5.1.22
Docker version 17.05.0-ce, build 89658be
docker-compose version 1.6.2, build 4d72027

GAE/Go 開発環境について

今回は下記サイトの手順で構築したものを使用します
takaya030.hatenablog.com

GAE/Go 環境での vendoring の注意点

  • app.yaml があるディレクトリ以下の go ファイルはビルド時にすべてリンクされるため、外部パッケージは別ディレクトリにインストールする (パッケージ内で syscall や unsafe を使用しているとエラーになるため、かつ必要なものだけをリンクするようにするため)
  • vendor ディレクトリは $GOPATH/src 以下に配置されていないと import の対象にならない

ディレクトリ構成

上記を踏まえた上でのディレクトリ構成

+---gaego
|   |   docker-compose.yml
|   |   
|   +---config
|   |   
|   +---data
|   |       Dockerfile
|   |   
|   +---glide
|   |       
|   +---sdk
|           Dockerfile
|            
+---logs
|    
+---www
    |       
    +---app
    |       app.yaml
    |       main.go
    |
    +---src
        |
        +---hello
                app.go
                app-engine.go
                app-standalone.go
                hello.go

各種ファイルの詳細

Echo を使って "Hello, World!" を表示するサンプルプログラムです
(前回記事から追加、変更のあるもののみ)

www/app/main.go

package main

import (
	_ "hello"
)

func init() {
}

www/src/hello/app.go

package hello

var e = createMux()

www/src/hello/app-engine.go

// +build appengine

package hello

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/engine/standard"
    "net/http"
)

func createMux() *echo.Echo {
    e := echo.New()

    // note: we don't need to provide the middleware or static handlers, that's taken care of by the platform
    // app engine has it's own "main" wrapper - we just need to hook echo into the default handler
    s := standard.New("")
    s.SetHandler(e)
    http.Handle("/", s)

    return e
}

// main()は書かなくて良い

www/src/hello/app-standalone.go

// +build !appengine,!appenginevm

package hello

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/engine/standard"
    "github.com/labstack/echo/middleware"
)

// ログの設定や静的ファイルの場所指定などをしています
func createMux() *echo.Echo {
    e := echo.New()

    e.Use(middleware.Recover())
    e.Use(middleware.Logger())
    e.Use(middleware.Gzip())

    e.Use(middleware.Static("public"))

    return e
}

func main() {
    e.Run(standard.New(":8080"))
}

www/src/hello/hello.go

package hello

import (
    "net/http"

    "github.com/labstack/echo"
    "github.com/labstack/echo/engine/standard"
    "github.com/rs/cors"
)

func init() {

    g := e.Group("/hello")
    g.Use(standard.WrapMiddleware(cors.Default().Handler))

    g.GET("", getWorld)
    g.GET("/:id", getUser)
}

func getWorld(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
}

func getUser(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, " + c.P(0) + "!")
}

glide のインストール

$ cd gaego
$ docker-compose run --rm sdk go get github.com/Masterminds/glide

glide.yaml の作成

$ cd gaego
$ docker-compose run --rm sdk /bin/bash -c "cd src/hello;glide create"
Starting gaego_data_1
[INFO]  Generating a YAML configuration file and guessing the dependencies
[INFO]  Attempting to import from other package managers (use --skip-import to skip)
[INFO]  Scanning code to look for dependencies
[INFO]  --> Found reference to github.com/labstack/echo
[INFO]  --> Adding sub-package engine/standard to github.com/labstack/echo
[INFO]  --> Adding sub-package middleware to github.com/labstack/echo
[INFO]  --> Found reference to github.com/rs/cors
[INFO]  Writing configuration file (glide.yaml)
[INFO]  Would you like Glide to help you find ways to improve your glide.yaml configuration?
[INFO]  If you want to revisit this step you can use the config-wizard command at any time.
[INFO]  Yes (Y) or No (N)?
N
[INFO]  You can now edit the glide.yaml file. Consider:
[INFO]  --> Using versions and ranges. See https://glide.sh/docs/versions/
[INFO]  --> Adding additional metadata. See https://glide.sh/docs/glide.yaml/
[INFO]  --> Running the config-wizard command to improve the versions in your configuration

glide によるパッケージインストール

上記操作で作成された glide.yaml では Echo の最新版がインストールされますが、go1.6 では動作しないので旧バージョンを指定するように変更します
(glide.yaml 4行目に "version: v2.2.0" を追加)

www/src/hello/glide.yaml

package: hello
import:
- package: github.com/labstack/echo
  version: v2.2.0          # この行を追加
  subpackages:
  - engine/standard
  - middleware
- package: github.com/rs/cors

その後、以下の操作でインストール

$ cd gaego
$ docker-compose run --rm sdk /bin/bash -c "cd src/hello;glide up"
Starting gaego_data_1
[INFO]  Downloading dependencies. Please wait...
[INFO]  --> Fetching github.com/labstack/echo
[INFO]  --> Fetching github.com/rs/cors
[INFO]  --> Setting version for github.com/labstack/echo to v2.2.0.
[INFO]  Resolving imports
[INFO]  --> Fetching github.com/labstack/gommon
[INFO]  --> Fetching github.com/dgrijalva/jwt-go
[INFO]  --> Fetching github.com/mattn/go-isatty
[INFO]  --> Fetching github.com/valyala/fasttemplate
[INFO]  --> Fetching golang.org/x/net
[INFO]  --> Fetching github.com/mattn/go-colorable
[INFO]  --> Fetching golang.org/x/sys
[INFO]  --> Fetching github.com/valyala/bytebufferpool
[INFO]  Downloading dependencies. Please wait...
[INFO]  Setting references for remaining imports
[INFO]  Exporting resolved dependencies...
[INFO]  --> Exporting github.com/valyala/bytebufferpool
[INFO]  --> Exporting github.com/labstack/echo
[INFO]  --> Exporting github.com/dgrijalva/jwt-go
[INFO]  --> Exporting github.com/mattn/go-colorable
[INFO]  --> Exporting github.com/rs/cors
[INFO]  --> Exporting github.com/mattn/go-isatty
[INFO]  --> Exporting github.com/labstack/gommon
[INFO]  --> Exporting github.com/valyala/fasttemplate
[INFO]  --> Exporting golang.org/x/sys
[INFO]  --> Exporting golang.org/x/net
[INFO]  Replacing existing vendor dependencies
[INFO]  Project relies on 10 dependencies.

以上で www/src/hello/vendor 以下に Echo v2 がインストールされました

動作確認

以下のコマンドで開発用サーバーが起動します

$ docker-compose up -d

web ブラウザで http://192.168.99.100:8080/hello にアクセスして "Hello, world!" の文字が表示されれば成功です
f:id:takaya030:20170805155642p:plain

http://192.168.99.100:8080/hello/takaya030 にアクセスすると "Hello, takaya030!" と表示されます
f:id:takaya030:20170805155700p:plain

ローカルの Docker 環境で Cloud9 IDE を動かす

Cloud9 IDE を Docker コンテナで動作させたときの手順メモ

検証環境

Windows10 Home Edition
VirtualBox 5.1.22
Docker version 17.05.0-ce, build 89658be
docker-compose version 1.6.2, build 4d72027

インストールにあたっての注意点

  • Cloud9 IDE は node.js で動作しているが、使用するバージョンは v0.10 または v0.12 とする
  • Cloud9 IDE のインストールは一般ユーザー権限で行う

ディレクトリ構成

+---c9
    |   docker-compose.yml
    |   
    +---workspace
    |   
    +---data
    |       Dockerfile
    |       
    +---ide
            Dockerfile

各種設定ファイル

c9/docker-compose.yml

version: "2"
services:
  data:
    build: ./data
    volumes:
      - ./workspace:/workspace
  ide:
    build: ./ide
    volumes_from:
      - data
    volumes:
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "8080:8080"
    command: node /home/docker/cloud9/server.js --port 8080 -w /workspace -l 0.0.0.0 --auth docker:tcuser

c9/data/Dockerfile

FROM busybox
LABEL maintainer "takaya030"

RUN mkdir -p /workspace
VOLUME ["/workspace"]
CMD ["true"]

c9/ide/Dockerfile

FROM ubuntu:16.04
LABEL maintainer "takaya030"

RUN apt-get update -y \
	&& apt-get install -y nodejs npm curl git sudo \
	&& apt-get clean \
	&& rm -fr /var/lib/apt/lists/*

RUN npm cache clean \
	&& npm install n -g

RUN n 0.12.18 \
	&& ln -sf /usr/local/bin/node /usr/bin/node \
	&& apt-get purge -y nodejs npm

# create docker user
RUN useradd -d /home/docker -m -s /bin/bash -u 1000 -g 50 docker

USER 1000
RUN git clone https://github.com/c9/core.git /home/docker/cloud9 \
	&& cd /home/docker/cloud9 \
	&& ./scripts/install-sdk.sh

USER root
RUN mkdir -p /workspace
VOLUME ["/workspace"]
WORKDIR /workspace

USER 1000

イメージのビルド

c9 ディレクトリに移動後、以下のコマンドでイメージをビルドする

$ docker-compose build

動作確認

以下のコマンドで Cloud9 IDE が動作します

$ docker-compose up -d

web ブラウザで http://192.168.99.100:8080 にアクセスすると Basic 認証で UserId と Password の入力を求められるので次の通りに入力する

  • UserId: docker
  • Password: tcuser

その後、以下の画像のように表示されれば成功です
f:id:takaya030:20170725232924p:plain

gcloud app deploy : This deployment has too many files

Laravel のプロジェクトを GAE にデプロイしたときに以下のエラーが発生したときの対処手順メモ

ERROR: (gcloud.app.deploy) Error Response: [400] This deployment has too many files. New versions are limited to 10000 files for this app.

エラー原因

Laravel のプロジェクトに Google APIs Client Library for PHP (google/apiclient) を追加したことでプロジェクトのファイル数が 10,000 を超えたため
(GAE は 1 バージョンあたり 10,000 ファイルまでしかアップロードできない)

# before installing google/apiclient
$ cd laravel
$ ls -FR | grep -v / | grep -v "^$" | wc -l
5688

# after installing google/apiclient
$ cd laravel
$ composer require google/apiclient:v2.1.3
$ ls -FR | grep -v / | grep -v "^$" | wc -l
10868

google/apiclient をインストールしただけで 5180 ファイルも増えている

対処方法

調べたところ増加したファイルの大半は google/apiclient-services のファイルだった
これらは Google の各種サービスにアクセスするためのクラス群であるが、ソースを見たところ各サービスは独立していて互いに参照していないように見受けられた
今回は Gmail API のみ使えればよいので、それ以外のファイルは削除した

$ cd laravel
$ mv vendor/google/apiclient-services/src/Google/Service/Gmail /tmp
$ mv vendor/google/apiclient-services/src/Google/Service/Gmail.php /tmp
$ rm -rf vendor/google/apiclient-services/src/Google/Service/*
$ mv /tmp/Gmail vendor/google/apiclient-services/src/Google/Service/
$ mv /tmp/Gmail.php vendor/google/apiclient-services/src/Google/Service/

$ ls -FR | grep -v / | grep -v "^$" | wc -l
5832

再度 "gcloud app deploy" を実行したところデプロイが正常に完了し、GAE で Gmail API が動作していることを確認した

Lumen でお手軽に Google API を使う

以前、Laravel から Google API を使う記事を書きましたが、今回は Lumen からより簡単に使ってみます

検証環境

$ php --version
PHP 5.5.34 (cli) (built: Jun 24 2017 00:40:52)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies

$ php artisan --version
Laravel Framework version Lumen (5.1.7) (Laravel Components 5.1.*)

OAuth クライアント ID、Refresh Token の取得

以下の記事を参考に Google API で使用する Client ID、Access Token を取得する
takaya030.hatenablog.com

Google APIs Client Library for PHP のインストー

前回 oauth2 ライブラリは oriceon/oauth-5-laravel を使用しましたが、今回は google/apiclient を使います

$ composer require google/apiclient:v2.1.3

サンプルコード

.env

.env に以下の内容を追加

# oauth
GOOGLE_CLIENT_ID=Your-Clinet-Id
GOOGLE_CLIENT_SECRET=Your-Clinet-Secret
GOOGLE_REFRESH_TOKEN=Your-Refresh-Token

app/Http/Controllers/GmailController.php

<?php

namespace App\Http\Controllers;

class GmailController extends Controller
{
    // show labels
    public function showLabels()
    {
        $client = new \Google_Client();
        $client->setClientId( env('GOOGLE_CLIENT_ID') );
        $client->setClientSecret( env('GOOGLE_CLIENT_SECRET') );
        $client->setScopes([ "https://mail.google.com/" ]);
        $client->refreshToken( env('GOOGLE_REFRESH_TOKEN') );

        $service = new \Google_Service_Gmail($client);

        // Print the labels in the user's account.
        $user = 'me';
        $results = $service->users_labels->listUsersLabels($user);

        $labels = [];
        if (count($results->getLabels()) == 0) {
            $labels[] = "No labels found.";
        } else {
            $labels[] = "Labels:";
            foreach ($results->getLabels() as $label) {
                $labels[] = sprintf("- %s", $label->getName());
            }
        }

        return view( 'gmail.labels', ['labels' => $labels] );
    }
}

app/Http/routes.php

<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/

$app->get('/gmail/labels', 'GmailController@showLabels');

resources/views/gmail/labels.blade.php

<!doctype html>
<html>
    <head>
        <title>gmail labels</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    @foreach($labels as $label)
        {{ $label }}<br />
    @endforeach
    </body>
</html>

動作確認

テストサーバー起動

$ php artisan serve

WEBブラウザで http://localhost:8000/gmail/labels を開いて下の画像のように表示されれば成功です
f:id:takaya030:20170715001557p:plain

まとめ

oriceon/oauth-5-laravel のときはエンドポイントの url を文字列で直接指定したり、レスポンスの json を自分でパースしなければならなかったりといろいろ手間がかかりましたが、google/apiclient ではそれらをライブラリ側で吸収してくれているので以前より簡潔なコードになっています

Go言語(golang)で Gmail の受信トレイにメールを追加してみる

Gmail API を使うとメール送信無しに Gmail の受信トレイに直接メールを追加することができます。その検証をしたときのメモ。

検証環境

Windows10 Home Edition

C:\>go version
go version go1.8 windows/amd64

Access Token の取得

以下の記事を参考に Gmail API で使用する Access Token を取得する
takaya030.hatenablog.com

oauth2 関連パッケージの取得

C:\>go get golang.org/x/oauth2
C:\>go get golang.org/x/oauth2/google
C:\>go get google.golang.org/api/gmail/v1

ソースコード

insertmail.go

package main

import (
  "fmt"
  "time"
  "log"
  "encoding/base64"

  "golang.org/x/oauth2"
  "golang.org/x/oauth2/google"
  gmail "google.golang.org/api/gmail/v1"
)

func main() {

  config := oauth2.Config{
    ClientID:     "Your Client ID",
    ClientSecret: "Your Client Secret",
    Endpoint:     google.Endpoint,
    RedirectURL:  "urn:ietf:wg:oauth:2.0:oob",  //今回はリダイレクトしないためこれ
    Scopes:       []string{"https://mail.google.com/"}, //必要なスコープを追加
  }

  expiry,_  := time.Parse("2006-01-02", "2017-07-11")
  token := oauth2.Token{
	AccessToken:  "Your Access Token",
	TokenType:    "Bearer",
	RefreshToken: "Your Refresh Token",
	Expiry:       expiry,
  }

  client := config.Client(oauth2.NoContext, &token)

  srv, err := gmail.New(client)
  if err != nil {
    log.Fatalf("Unable to retrieve gmail Client %v", err)
  }

  // email date
  const rfc2822 = "Mon Jan 02 15:04:05 -0700 2006"
  email_date  := time.Now().Format(rfc2822)

  // an email data
  message := gmail.Message{
    Raw: base64.URLEncoding.EncodeToString([]byte("Date: " + email_date + "\r\n" +
         "From: hoge@example.com\r\n" +
         "To: fuga@gmail.com\r\n" +
         "Subject: test mail\r\n" +
         "Content-Type: text/html; charset=UTF-8\r\n" +
         "\r\n" +
         "<html><body>This is a test mail.</body></html>")),
    LabelIds:       []string{"INBOX"},
  }

  // insert message
  m, err := srv.Users.Messages.Insert("me", &message).Do()
  if err != nil {
    log.Fatalf("Unable to insert messages. %v", err)
  }
  fmt.Printf("Finish inserting message. id:%s", m.Id)
}

ビルド

C:\go_src>go build insertmail.go

動作確認

C:\go_src>insertmail.exe
Finish inserting message. id:15d37199b2d9xxxx

Gmail の受信トレイ(INBOX)に以下のメールが追加されていれば成功です
f:id:takaya030:20170712232850p:plain

Go言語(golang)で Gmail API を使う

検証環境

Windows10 Home Edition

C:\>go version
go version go1.8 windows/amd64

Access Token の取得

以下の記事を参考に Gmail API で使用する Access Token を取得する
takaya030.hatenablog.com

oauth2 関連パッケージの取得

C:\>go get golang.org/x/oauth2
C:\>go get golang.org/x/oauth2/google
C:\>go get google.golang.org/api/gmail/v1

ソースコード

gmailtest.go

package main

import (
  "fmt"
  "time"
  "log"

  "golang.org/x/oauth2"
  "golang.org/x/oauth2/google"
  gmail "google.golang.org/api/gmail/v1"
)

func main() {

  config := oauth2.Config{
    ClientID:     "Your Client ID",
    ClientSecret: "Your Client Secret",
    Endpoint:     google.Endpoint,
    RedirectURL:  "urn:ietf:wg:oauth:2.0:oob",	//今回はリダイレクトしないためこれ
    Scopes:       []string{"https://mail.google.com/"}, //必要なスコープを追加
  }

  expiry,_  := time.Parse("2006-01-02", "2017-07-11")
  token := oauth2.Token{
	AccessToken:  "Your Access Token",
	TokenType:    "Bearer",
	RefreshToken: "Your Refresh Token",
	Expiry:       expiry,
  }

  client := config.Client(oauth2.NoContext, &token)

  srv, err := gmail.New(client)
  if err != nil {
    log.Fatalf("Unable to retrieve gmail Client %v", err)
  }

  r, err := srv.Users.Labels.List("me").Do()
  if err != nil {
    log.Fatalf("Unable to get labels. %v", err)
  }

  if (len(r.Labels) > 0) {
    fmt.Print("Labels:\n")
    for _, l := range r.Labels {
      fmt.Printf("- %s\n",  l.Name)
    }
  } else {
    fmt.Print("No label found.")
  }
}

ビルド

C:\go_src>go build gmailtest.go

動作確認

C:\go_src>gmailtest.exe
Labels:
- CATEGORY_PERSONAL
- プライベート
- 旅行
- CATEGORY_SOCIAL
- 領収書
- IMPORTANT
- 仕事
- CATEGORY_UPDATES
- CATEGORY_FORUMS
- CHAT
- SENT
- INBOX
- TRASH
- CATEGORY_PROMOTIONS
- DRAFT
- SPAM
- STARRED
- UNREAD