スマレジ API・webhook を使うための手順

背景

  • 小売業の社内システムで、「レジでピッてしたらシステムに売却済みにしてほしい」と言われた
  • 国内で API/webhook が生えてるPOSはスマレジだけっぽい
    • Square ありそうだけど、スマレジに比べると導入ハードルが高そう

記事の内容

  • スマレジの POS 機能の API/Webhook を使えるようにするための手順
  • 実装の詳細は書いてないよ

前提

スマレジのシステム的なもの

  • 管理画面: PCのブラウザから、商品登録や店舗の登録、売上管理をやる画面
  • スマレジ・デベロッパーズ: 開発者用アカウントにログインして、API や Webhook 周りの各種設定をやるためのサイト
  • スマレジアプリ: 会計や精算といった店舗業務をやるためのスマホアプリ

手順

1. スマレジ・デベロッパーズのアカウントを作る

  • API や Webhook を使うためには、スマレジ本体とは別の開発者用アカウントが必要になる
  • 開発者用アカウントは「スマレジ・デベロッパーズ」でつくる
  • メアドとパスワードを設定してすぐ登録できる

この後の大まかな流れとしては、デベロッパーズの中で「アプリ」というものをつくって、アプリに発行される client key/secret を使って API の認証をしたり、Webhook endpoint を設定したりする

2. スマレジ・デベロッパーズで「アプリ」を作る

  • アプリにはプライベートアプリとパブリックアプリがある
    • 「自分の店だけで使いたい」というユースケースならプライベートアプリにすると審査もいらなくてスムーズ
    • 逆にいろんな人に使ってほしい・サービスでお金を取りたい場合はパブリックにするけど、審査が必要
  • アプリごとにクレデンシャルや Webhook endpoint が設定できる
  • API・Webhook を使うための手順は後述するが、基本的にアプリの中で設定すれば OK

3. サンドボックス環境にログイン

  • デベロッパーズのアカウントを作ると、実際の POS 管理画面 (web) や iPhone/iPad のレジアプリを使うための「サンドボックス環境」が払い出される
    • 別途スマレジ本体でアカウント登録は不要
  • サンドボックス環境は契約IDが sb_ で始まる
  • web の管理画面は https://accounts.smaregi.dev/login から、デベロッパーズの方と同じクレデンシャルでログインする
  • 「レジで商品をスキャンする」は、バーコードリーダーがなくても iOS アプリでバーコードを読んだり、商品一覧から選択して会計まで進めることができる
  • サンドボックス用の iOS アプリはスマレジアプリをダウンロードした後にデベロッパーズのクレデンシャルでログインすれば使える
    • デベロッパーズの方にある QR コードをスキャンしても OK (アプリではログアウトした状態)
    • 設定 > アプリ環境切替

web の管理画面とスマホアプリが用意できたら実機テストの準備は完了

4. API/Webhook の設定 (ざっくり)

API を使う場合に設定すること

  • 環境設定タブ: 開発・本番環境の client key/secret を取得・設定
    • client secret は初めて発行する場合でもボタンが「再発行」となっているが気にせず押して OK
    • スマレジの API を叩くときにこの client key/secret を使って
  • スコープタブ: API のスコープを設定
    • e.g. pos.products:read

Webhook を使う場合に設定すること

  • 環境設定タブ: 開発・本番環境の Webhook エンドポイント
  • Webhook タブ:
    • 「利用する」を ON にする
    • (推奨) スマレジから送ってもらうカスタムヘッダー
      • なくても動くけどセキュリティ的にあった方が良い...と思っている
      • 複数設定できるので環境別にヘッダーの値をわけることも可能
    • 送信するイベント e.g. 取引

逆にあんまり関係ない設定

  • 環境設定タブ
    • アプリの URL
    • 利用者契約通知先 URL
    • リダイレクト URI

5. 開発する

  • レファレンスはここ: https://developers.smaregi.dev/platform-api-reference/
  • (感想) 取引の Webhook を処理したかったけど、レファレンスを読むだけだと ??? だったので、とりあえず dump するだけの endpoint を用意するとわかりやすかった
  • サンドボックス環境で動かしたもの (e.g. スマホアプリからの取引) デベロッパーズ側の開発環境の設定が使われる
  • 本番環境で動かすためには次の手順が必要
  • スマホアプリを使うと「レジでピッてやる」ができる。使い方は後述

6. 本番環境のアクティベート

  • 本番環境の POS を使いたい場合、まずスマレジ本体の方に申し込みが必要
    • ここは自分でやってないので割愛するけど、無料プランがあるので0円で始められそう
  • デベロッパーズ側では、本番環境の契約IDってやつを「アクティベート」する必要がある
    • 契約IDは、画面の左上に出てくる (デベロッパーズ・管理画面どっちも)
  • ↓の画面に契約IDを入れると、お店側で登録したメールアドレスにメールが飛ぶので、メールにある承認リンクをクリックする必要がある
    • e.g. デベロッパーズに developer@example.invalid で登録してて、お店側は store@example.invalid でログインしていたら承認メールは後者に行く

スマレジアプリ入門

⚠️ 開発するためのちょー基本的なことしか書いてないので、店舗業務の参考にはしないでね

前提

  • スマレジのアプリを使うと、実際に店舗スタッフのように商品のバーコードをスキャンしたりお会計をしたりできる
  • 商品の入力方法は3つ
    • 商品一覧から選ぶ: テストはこれが楽

メイン画面

アプリを開くとこの画面が出てくる。メニューからやりたいことの画面に遷移する。

よく使うのはこの3つ↓

  • 販売業務: 商品を入れてお会計をする。店舗スタッフがレジを打つときに使う画面
  • 取引履歴: 名前の通り。取引をキャンセルしたいときもここからやる
  • 設定: ログイン/ログアウトとか、商品をどうやって入力するか (バーコードスキャン、一覧から選択、手動でコード入力) の設定とか

商品入力→お会計

メイン画面から販売業務をタップするとこの画面になる。真っ白なので一瞬焦るが、画面下部のナビゲーションバーの左から2番目のアイコンを選ぶと、商品のコード入力やバーコードスキャンができる。商品の入力方法は設定画面から選べて、バーコードスキャン (カメラを使う)・商品リストから選択・手動でバーコード入力の3つの方法がある。テスト用には2番目が楽。

会計に進む場合は、合計金額と数量が表示されているバーをタップする。「預り金」の部分をタップすると金額が入力できるので、合計金額以上の数字を入力する。

割引・値引き

割引・値引きは商品単位と会計単位で設定できる (値引き=固定の金額を引く、割引は10%OFFみたいな率を設定) 。

商品単位:

会計単位:

データ同期について

実はスマレジの管理画面で商品を登録しても、すぐにレジで打てるようにはならない。スマレジアプリの方には「データ同期」という概念があって、商品を登録・更新してから次のデータ同期がアプリ側で走らないと変更が反映されない。

データ同期は自動でも走るし、設定画面から手動で最新データを引っ張ることもできる。設定 > データ > データ管理 で進むと、「店舗」と「商品」それぞれの同期時刻が表示されるので、更新したい方の行をタップする。

まとまってない tips

取引 Webhook event の payload

payload は↓のようなかたちで、実は1取引1 event ではなく、1 event につき複数の取引が transactionHeadIds に含まれうる。

{
  "contractId": "string",
  "event": "string",
  "action": "created",
  "transactionHeadIds": [
    "1",
    "10",
    "12"
  ]
}

取引 の action とは

取引の action (操作の種類) には以下のものがある。

  • created: 登録
  • edited: 更新
  • canceled: 取消
  • disposed: 返品取消
  • bulk-update: 一括更新
  • bulk-deleted: 一括削除

雑多に調べたことを書くと、

  • edited: 金額や商品の変更はなくて、タグや客層といったメタデータの変更
    • 感想: 1取引 ID x 1 action で複数メッセージがありうるので dedupe がむずかしそう
    • 今回使わなかったが、処理する必要があれば out-of-order とかもありえそうなので、edited の Webhook が来たら API で取引の最新情報を取得するアプローチが良さそう?
    • ただ、一方で Webhook のイベントごとに API を叩くのは推奨されていないので (rate limit がある)、バッファしたり工夫が必要そう
  • canceld VS disposed:
    • canceled (取消) は、同じ取引を編集して取引をなかったことにする
    • disposed (返品取消) は、消込の取引を作成して取引をなかったことにする
    • 嬉しいポイント: 店舗ごとの設定で、「返品取消のみ」にすることが可能。システム的にはこれが楽なので今回はこの設定にした
  • created, disposed は取引あたり一回しか起きないイベント
    • 明示的に docs にあったわけではないが、UI とロジック?的にそうだと思う

商品のバーコード

今回、ビジネス要件が「レジでピッてしたら社内システムに反映してほしい」というものだったので、バーコードについても少し調べた。

  • バーコードのしくみ:
    • バーコードの実態はただの番号 (英数字が含まれる場合もある)
    • バーコードリーダーピッてすると、番号が Bluetooth 等を経由して POS アプリ側に勝手に入力される ... というのがスキャンのしくみ
  • スマレジの運用例:
    • スマレジで、「商品コード」を00001に設定した商品を登録
    • ラベルプリンター (スマレジのやつがある) で、バーコードを印刷
    • バーコードが張られた商品をスキャンすると、スマレジアプリの明細にその商品が追加される
  • つまり、バーコードの番号 = スマレジの商品コード
  • バーコードには種類があって、社内独自の番号を採番するやり方と、JAN コードを使うやり方がある
    • JAN コードは世界共通の識別番号で、申請が必要
    • なので、自社内で売るだけなら基本的に前者のやり方で良いはず (000001 とか)
  • バーコードには規格もあって、規格によって「数字だけ使える」「アルファベットなど数字以外も使える」という点が違う
    • 一番汎用的なのは JAN コードで使われている「EAN 規格」で、これは数字だけ使える
    • バーコードリーダー・ラベルプリンターが使いたい規格に対応しているかの確認が必要
    • (自分は数字だけが一番丸いと思ったのでハイフンとかアルファベットは使わない自動採番にした)

周辺機器

  • バーコードリーダーを買いたい場合、スマレジストアから買わないとスマレジの保証外になると書いてあったものの、リーダー1台3万くらいした
  • とはいえ、スマホアプリがあればバーコードスキャンもできるのでテストの範疇ではリーダー不要
  • スマレジで便利にバーコードのラベルを商品を貼りたい場合、スマレジのラベル印刷用アプリとラベルプリンターの2つが必要。アプリが月ごとに課金するタイプなので微妙な気持ちになった。節約したかったらラベルプリンターだけ用意して、印刷はスマレジから商品の CSV をダウンロードしてごにょごにょすればできそう

おすすめバーコードスキャンのテスト方法

  1. スマホに一般的なバーコードスキャンアプリを入れる (スキャンすると番号を出してくれるやつ)
  2. 机の周りにありがちな物体をスキャンして、番号を取得 (ガムとかのど飴とか)
  3. スマレジで商品登録をして、商品コードをこのバーコードに設定する

→ これで、「レジでピッてしたら自分のシステムに Webhook が飛ぶ」が試せる

アプリまわり

  • パブリックアプリ (いろんな人に使ってもらったり収益化できたりする) の場合は審査が必要
  • プライベートは不要
  • サンドボックの契約ID (sb_ で始まるやつ) をプライベートアプリでアクティベートしようとするとエラーになる

参考

デベロッパーズアカウント(開発環境)でアプリの検証をする – スマレジ・ヘルプ

おわりに

バーコードスキャンのしくみすらよくわかってないので、「レジでピッてしたらシステムに飛ばす」の環境を準備するまでが大変だった

Prisma の migration 中に pg_advisory_lock タイムアウトした

起こったこと

Vercel で prisma migrate reset --force を実行したら以下のエラー文が出た

Context: Timed out trying to acquire a postgres advisory lock (SELECT pg_advisory_lock(72707369)). Timeout: 10000ms. See https://pris.ly/d/migrate-advisory-locking for details.

調査

  • prisma は複数の migration が走らないように、 prisma migration 用のロックを持つ
  • ロックは object id = 72707369 で固定
  • 基本はロックを解除して migration を終わるので問題ない
  • が、 migration が途中でコケるとロックが解除されず、次の migration でロックが取得できない

参考: https://medium.com/israeli-tech-radar/fixing-a-stuck-postgres-advisory-lock-without-restarting-your-db-02c33d0f0c08

対応

  • advisory lock の object ID から pid を取得
  • pg_terminate_backend (!) で pid を解放する

(SQLkill 的なことができるんやという感動)

-- step 1: check the lock exists
SELECT * from pg_locks where objid = 72707369

-- step 2: using the pid obtained from step 1, check that there is an activity
SELECT * FROM pg_stat_activity WHERE pid = 441;

-- step 3: kill the process
SELECT PG_TERMINATE_BACKEND(441);

Vercel OIDC で GA4 の Data API を叩く

背景

  • GA4 の Data API を使って PV 数をダッシュボードに表示したかった
  • 今回のアプリケーションは GCP ではなく Vercel にデプロイしている
  • どうやら Vercel の OIDC を使えば service account key を突っ込まなくても認証できそう

ということでやってみたんだけど、ドキュメントが若干不親切だったり自分が Workload Identity をわかってなかったりして詰まったのでめも

概要

やることは基本ここ (https://vercel.com/docs/oidc/gcp) の通りで、だいたいこんな流れ↓

  • GCP
    • Workload Identity Pool の作成
    • Service Account の作成 & Workload Identity User で紐づけ
  • アプリケーション
    • Vercel の OIDC token を取得
    • ExternalAccountClient.fromJSONトークンを渡す
  • GA4
    • サービスアカウントにプロパティの「閲覧者」権限を付与

作業

Vercel の Team にいない場合の Workload Identity 設定

Vercel の OIDC のドキュメントのつらみとして、step 3 で issuer URL を発行するところでは Team の中に Project があるケースとそうでないケースを分けて説明してくれるのに、それ以降は Team がある場合の説明しかないという1つあった 。が、 Team を明示的につくっていない場合でも、 Vercel には "Default Team" という概念があり (Account Settings > Default Team で見れる)、結果として Hobby プランにいる (i.e. Team がつくれない) 自分でも Team ありの手順と同じでよかった。

たとえば、Workload Identity Provider を設定するとこでは

Enter the Issuer URL, the URL will depend on the issuer mode setting:

  • Team: https://oidc.vercel.com/[TEAM_SLUG], replacing [TEAM_SLUG] with the path from your Vercel team URL
  • Global: https://oidc.vercel.com

と書いてあるけど、TEAM_SLUG は Default Team の slug を入れれば OK。Default Team は、Account Settings からも確認できるし、自分の Project のダッシュボードを Vercel で開けば、 https://vercel.com/xxxxx/<project name> となっているはずなので、この xxxxx の部分が Default Team の slug になる。

Step 8 の IAM Principal の設定でも、同様に principal://.../subject/owner:acme:project:my-project:environment:production となっているところでは acme の部分に Default Team slug を入れる。

Service Account User ではなく Workload Identity User かも

Step 8 で、なぜか principal に Service Account への Service Account User ロールを付与しているけど、 Workload Identity なので Workload Identity User が正しい気がする。Service Account User での動作確認はしていないけど、

  • なんのロールも principal につけていないと iam.serviceAccounts.getAccessToken の権限不足と言われる
  • → Service Account User にはこの権限はない
  • → おそらく Workload Identity User が正しい権限

だと思っている

Workload Identity Pool 設定

terraform で書くならこう

resource "google_iam_workload_identity_pool" "vercel" {
  workload_identity_pool_id = "vercel"
  display_name              = "vercel"
}

resource "google_iam_workload_identity_pool_provider" "vercel" {
  workload_identity_pool_provider_id = "vercel"
  workload_identity_pool_id          = google_iam_workload_identity_pool.vercel.workload_identity_pool_id
  display_name                       = "vercel"

  attribute_mapping = {
    "google.subject" = "assertion.sub"
  }

  oidc {
    # see: https://vercel.com/docs/oidc/gcp#add-a-provider-to-the-identity-pool
    issuer_uri = "https://oidc.vercel.com/${var.vercel_team_slug}"
    allowed_audiences = [
      "https://vercel.com/${var.vercel_team_slug}"
    ]
  }

}

resource "google_service_account_iam_member" "vercel_worker_workload_identity_user" {
  service_account_id = var.vercel_worker_service_account_id
  role               = "roles/iam.workloadIdentityUser"
  member             = "principal://iam.googleapis.com/${google_iam_workload_identity_pool.vercel.name}/subject/${var.vercel_oidc_subject}"
}

GA4 との接続

GA4 の Data API を Node.js でやるコード例はここ: https://developers.google.com/analytics/devguides/reporting/data/v1/quickstart?account_type=service

GOOGLE_APPLICATION_CREDENTIALS の環境変数を設定している場合は上記のように new BetaAnalyticsDataClient() をするだけでいいんだけど、今回は違うやり方なのでこれだと動かない

Vercel のドキュメントからは ExternalAccountClient を使うと何かしらの credential が取れるのはわかるが、GCP 側のドキュメントがあまりにも手薄でわからなかった。AI に書いてもらって、type error も起きてないし動作確認もとれたのでいったん↓を使っている。

import { BetaAnalyticsDataClient } from "@google-analytics/data"
import { ExternalAccountClient, GoogleAuth } from "google-auth-library"
import { getVercelOidcToken } from "@vercel/oidc"

const authClient = ExternalAccountClient.fromJSON({
  type: "external_account",
  audience: `//iam.googleapis.com/projects/${process.env.GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${process.env.GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${process.env.GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID}`,
  subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
  token_url: "https://sts.googleapis.com/v1/token",
  scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
  service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${process.env.GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken`,
  subject_token_supplier: {
    getSubjectToken: getVercelOidcToken,
  },
})

const googleAuth = new GoogleAuth({
  authClient: authClient,
})

const client = new BetaAnalyticsDataClient({ auth: googleAuth })

tips

ローカルで動作確認をしたい

  • vercel env pull とやると、 development 環境の VERCEL_OIDC_TOKEN (JWT) が見れるので、 デコードすればどんな isssub が入っているか見れる
  • vercel env pull は既存の .env を上書きするので、困る場合は vercel env pull .env.vercel みたいにファイル名を指定する
  • vercel env ls だと出てこない
  • production の token を見ようと思って vercel env pull --environment production をやってみたけど、なぜか subenvironment:development になっていた (本番では environment:production になってる)

pool, provider, principal の整理

  • pool: 全体のハコ。ID と name くらいしかない (あとは enabled/disabled)。
  • provider: GitHub Actions とか Vercel とかとの外部の認証プロバイダ。接続設定的なイメージ。
  • attribution mapping: google.subject = assertions.sub は、 JWT の sub フィールドを Google
    • 他にも GitHub Actions だとattribute.repository = assertions.repository みたいなマッピングをすることが多い
    • attribute condition で、認証を通す principal をさらに制御することもできるが、これは JWT の assertion 直ではなく、いったん attribute としてマッピングしたものに対して条件をつける
  • principal: Service Account をつくる → Service Account にロールをつける → Workload Identity User で 外部の principal と Service Account を紐づける → 外部の principal が GCP 上の権限を持てる
    • principal://iam.googleapis.com/projects/012345678901/locations/global/workloadIdentityPools/vercel/subject/owner:acme:project:my-project:environment:production だったら、 JWT の subowner:acme:project:my-project:environment:production になっていれば、紐づけた Service Account の権限がもらえる
    • 逆に、 sub がたとえば ...:environment:development とかなってたら権限エラーになる

AWS で switch role をして必要な権限を探す方法 めも

preamble

久々に AWS を触ったら何もかも忘れていたので備忘録

  • やりたいこと: 別の人の AWS アカウントで、自分の作業に必要な権限を特定したい
  • おきもち: やっぱり AWS の権限モデルすぐ忘れる、Google Cloud の方がわかりやすい (個人の感想)

overview

  1. AWS で新しく権限を絞ったロールをつくる
  2. 自分のアカウントで switch role する
  3. 作業ができるか試す

ロールをつくる

(1) IAM > Roles に行って、 Create Role ボタンを押す

(2) "Select Trusted Entity" で、 "AWS account" を選択 → "This account" を選択

ちなみに、

Allow entities in other AWS accounts belonging to you ...

と書いてあるので紛らわしいけど、 "other" じゃなくてこの AWS account 内でも大丈夫

(3) Add permissions で必要な権限を選択

(4) "Name, review, and create" で名前をつけて保存

スイッチロール

ロールをつくると、右上に switch role のリンクが出てくるので、それを踏む

実際にスイッチロールすると、他のタブで AWS のコンソールを開いていたとしても強制的にサインアウトされるので注意かも。あとはこのロールで作業できるか確認して、足りなかったら適宜権限追加して... を繰り返す。

まとめ (?)

書きながら思い出しててロールを作っちゃえば、あとはかんたんだった。とはいえ毎回 trust relationship の概念を忘れる..

  • trust relationship: 誰がこのロールを assume できるか?
  • arn:aws:iam:<account ID>:root を trust relationship で許可しておくと、指定した account ID の誰でも assume role できるようになる

参考

Classmethod さんの記事大好きなんだが、たくさんあってどれ見たか忘れちゃうのでめも (感謝)

スイッチロールをやってみた | DevelopersIO

IAMユーザーにAssumeRoleの権限が無くてもスイッチロールできる(ように見える)のはなぜですか? | DevelopersIO

Next.js の session まわりめも

セッション周りの理解がガバすぎて、ここを読んでよくわからなかったのでめも。違ってたらぜひ教えてほしい

https://nextjs.org/docs/app/guides/authentication

session とは

毎回忘れるけど、"ページをまたいで受け渡されるデータ" が session data で、 DB とか Cookie とかはそれをどう保存するかという話

余談: session ID って危なくないんだっけ

session ID ってブラウザの dev console からいじれるから、 session ID をテキトーに変えたら他の人の情報とれるんじゃね? と思って なんで大丈夫なんだっけというのを調べた

(1) session IDs are cryptographically secure

cryptographically secure → 予測されにくい

(2) HttpOnly x 短い expiresAt

  • HttpOnly = JS で操作できない = 人手でぽちぽち変えるしかない
  • これに、 expiresAt が組み合わさると、制限時間内に brute force することはほぼ無理

なので、予測されにくい session ID と、 cookie の HttpOnly と、 expiresAt の設定によって守られてる

stateless session とは

個人的に stateless っていう名前が超紛らわしいと思ったんだけど、 stateless っていうのはただ「session data を DB に保存してないですよ」っていうだけだった

server-side/client-side cookies

cookie ってブラウザに保存するやつだから全部 client-side では..? と思って謎だった 結論これはあってて、 server/client は「誰が」 cookie をセットするかの違いだった

  • client-side: JS から document.cookie APIcookie を set する
  • server-side: レスポンスに Set-cookie ヘッダーを入れる → ブラウザに cookie を保存 → 次のリクエストで cookie を入れる

zod x React Hook Form で transform するときに型の整合をとる

zod x React Hook Form (RHF) で、日付とか時間とかを扱うときに zod の form schema と RHF の型が合わないことがあるっていう話 結論 onChangevalue でやるのがよさそう

const formSchema = z
  .object({
    date: z.coerce.date<Date>().optional(), // 入力は Date
  })

type FormSchema = z.infer<typeof formSchema>


export function Form() {
  const form = useForm<FormSchema>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      date: new Date(), // ここも string だとエラーになる
    },
  })

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField control={form.control} name="date" render={({ field }) => (
          <FormItem>
            <FormLabel>Date</FormLabel>
            <FormControl>
              <Input 
                type="date"
                {...field}
                onChange={(e) => field.onChange(new Date(e.target.value))} // RHF には date を渡す
                value={field.value ? formatDate(field.value) : ""} // input には string を渡す
              />
            </FormControl>
          </FormItem>
        )} />
      </form>
    </Form>
  )
}

さくらのメールボックスを使ってみた

背景

Google Cloud でドメインを取ったんだけど、このドメインのメールアドレスが必要になった。サービス的に Google Workspace は 1mm もいらないし、かといってこれまで使ってたムームーだとムームー以外で取ったドメインは使えないらしい*ので、サービスを探したところ、なんだかさくらのメールボックスが一番安そうだった。

調査

まずはどんなサービスですかということで調べてみたところ、初見だとちょっとわかりにくいところがあった。

前に使ってたムームーは直感的な作りになってて、ドメイン (example.com) を登録して、そのドメインに対するメアド (support@example.com) を淡々と作っていけばメールが使えるようになっていた。

さくらは色々とフクザツな仕様になっていて、たとえば独自ドメイン (さくら以外でとったドメイン) は使えるけど、

  • 独自ドメインとして foo.combar.com を登録していて、
  • info@foo.com を登録したいとすると、
  • info@foo.cominfo@bar.com のメアドが払い出されて、
  • 両方の info のメアドが同じ受信箱に届く

... というのがデフォルトの挙動らしかった。

ワンチャン複数のドメイン/サービスを同じメールボックスで管理する可能性があったので、これだとちょっとまずいなと思っていたが、もうちょい調べてみると、設定次第では最安プランでも受信箱を分けられる設定ができることがわかったので、改めてさくらで進めることに決定した。

申し込み

申し込みは素直に「さくらのメールボックス」を選択して進めればできるんだけど、一点わかりにくかったとことして、↓の選択肢がある。

前述の通り、ドメインはもうとってあるのであらためて取得する必要はないんだが、「レンタルサーバーだけ」というのがよくわからなくて、メールボックス = レンタルサーバ ...? など混乱した。結論としては「レンタルサーバだけ」で大丈夫で、メールボックスを動かすようのサーバをさくら側で払い出すからこういう表現になるのかなと解釈した。

料金に関して、2025年8月現在さくらのメールボックスは「月額88円〜」という表記になっている。この金額は36ヶ月を一括で契約した場合で、いちばん短い契約は12ヶ月単位の110円/月だった。現時点だといつまでこのメールボックスを使うのか謎なので自分たちは12ヶ月にした。

設定: DNS

契約してまず最初にやることは DNS の設定で、取得したドメインのメアドを作れるようにした。

これに関しては↓の記事を見ておけば OK (マジ感謝)

あえて付け加えるとすれば、自分は terraform で DNS の設定をしていて、 rrdatas に255文字以上の要素があると 400 が Google Cloud から返ってくるのでこういう書き方をした↓

  dkim_prefix = "v=DKIM1; k=rsa; p="
  dkim_full   = "${local.dkim_prefix}${var.dkim_public_key}"
  # Split at 255 characters for Google Cloud DNS
  # see: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/dns_record_set#rrdatas-1
  dkim_part1     = substr(local.dkim_full, 0, 255)
  dkim_part2     = substr(local.dkim_full, 255, -1)
  dkim_formatted = "\"${local.dkim_part1}\"\"${local.dkim_part2}\""
}

resource "google_dns_record_set" "dkim" {
  count = var.email_domain_primary != "" ? 1 : 0
  name  = "${var.dkim_selector}._domainkey.${var.domain}."
  type  = "TXT"
  ttl   = 300

  managed_zone = var.dns_managed_zone_name

  rrdatas = [local.dkim_formatted]
}

see: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/dns_record_set#rrdatas-1

設定: メールアドレス

ここでやりたいのは、登録した独自ドメインそれぞれに対して、 info@foo.cominfo@bar.com の受信箱を分けられるような設定を入れること。

前提として、さくらのメールボックスには「ユーザ」という概念がある。メールアドレスとニアリーイコールではあるんだけど、@ の前が「ユーザ名」で、たとえば info というユーザをつくると、登録したすべてのドメインに対して info@xxx.sakura.ne.jp, info@foo.com など複数のアドレスが払い出される。

info@foo.cominfo@bar.com が同じ受信箱で見れちゃうのはきもちわるいので、次の手順でドメインごとに受信箱を分けられるようにする。

(1) ドメインの設定を個別受信にする

↓のヘルプページに沿って、ドメインの「受信するかどうかを個別に設定する」にチェックを入れる。

個別ドメイン受信設定をしたい | さくらのサポート情報

(2) メールエイリアスとユーザを設定する

メールエイリアスっていうのは、メールアドレスに対して受信するユーザを設定するもので、↓のような画面で設定する。

(https://help.sakura.ad.jp/mail/2870/#heading-3 より引用)

ポイントとなるのが、受信するユーザというところで、このユーザは結局全ドメイン共通になる。じゃあどうするかというと、 foo.com というドメインを登録していたら、 info-foo というユーザを作成して、 info@foo.comエイリアスinfo-foo ユーザを設定する。

手順としては

  • info-foo ユーザを作成する
  • メールエイリアスinfo@foo.cominfo-foo ユーザ と設定する

... となる。

なんだか微妙に腑に落ちないけど、一応これで受信できるのは確認できた。

もうちょっと考えてみると、ヘルプページには

「受信するかどうかを個別に設定する」を選択すると、受信専用メールアドレスの設定(メールエイリアスの設定)を行うまで、設定したドメインでメールは利用できません。

とあって、個別受信の設定で foo.combar.com があったとすると、

  • info-foo ユーザを作成して info@foo.com の受信先に設定する
  • この時点では info@bar.comエイリアスが貼られてないので受信先がない
  • なので、 info@foo.cominfo-foo ユーザしか見れない

... ということだと思う。うーん。

Gmail の設定

メールエイリアスを設定した上で、 GmailPOP3/IMAP/SMTP の設定ができるのかが地味に心配だったけど、結論できた。

設定する上では、

  • ユーザ名: エイリアスのメールアドレス info@foo.com を入力
  • パスワード: ユーザの info-foo@foo.com のパスワードを入力

とすれば OK で、受信するのも info@foo.com として Gmail から送信するのもできた。

メールソフト(メーラー)の設定内容を知りたい | さくらのサポート情報

感想

MX レコードについて

MX レコードと複数ドメインのまわりで気になったことがあったのでメモ。

→ サービスA, サービスB でおなじメールボックスを使うと、両方の MX レコードに同じさくらのドメインが登録される

なので、基本的に問題にはならないと思うけどサービスA, B が同じ運営とバレたくないときはメールボックスを分けた方がよさそうかも。

サービス

結局上記の MX レコード/初期ドメイン問題があるので、サービスA, B のメアドをさくらのメールボックスで管理したいとなった場合は、別々のさくらのメールボックスを契約した方がいいんでは? の気持ちになった。そうなった場合、個別受信とかの設定を考える必要がないので、自分で問題を余計に複雑にした説はありつつ、 DNS の設定とかはちょっとめんどくさかった。みんなはメールサービス何使ってますか?