この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Amazon SESの送信制限を超えた際のエラーメッセージと、挙動を確認したかったので簡単な検証しました。
結論
- 24時間以内の送信制限数を超過すると
Daily message quota exceeded
エラーが返される。 - 秒間の最大送信レートを超過すると
Maximum sending rate exceeded
エラーが返される。 - SESのCloudWatchメトリクスに送信制限数の項目はない。SESのダッシュボードか、AWS CLI、SDKで確認できる。
- 送信制限に引っかからないかモニタリングしたい場合、自前でカスタムメトリクスを取得する必要がある。
Errors related to the sending quotas for your Amazon SES account - Amazon Simple Email Service
送信制限の確認
アカウントダッシュボードから送信制限値を確認できます。
また、aws ses
コマンドからも送信制限値を確認できます。
$ aws ses get-send-quota
{
"Max24HourSend": 200.0,
"MaxSendRate": 1.0,
"SentLast24Hours": 0.0
}
現在の送信制限値
SESはサンドボックス環境です。制限値は低い値になっています。制限値が低いため容易に超過することができるので試してみます。
項目 | 値 | 備考 |
---|---|---|
Maximum daily sends | 200通/24時間以内 | 制限値を超えると送信できない |
Maximum sending rate | 1通/秒 | 短時間であれば制限を超えることができる 制限を超過したまま持続することはできない |
送信制限の詳細
最大送信レート
1秒に1通のメールを合計10通送信してみます。
$ go version
go version go1.16.2 darwin/amd64
main.go
package main
import (
"fmt"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
)
const (
Sender = "[your-ses-domain]"
Recipient = "[your-mailadress]"
Subject = "SES Test Mail"
Body = "Test mail count "
CharSet = "UTF-8"
)
func main() {
sess := session.Must(session.NewSession())
svc := ses.New(
sess,
aws.NewConfig().WithRegion("ap-northeast-1"))
startFor := time.Now()
for i := 0; i < 10; i++ {
sendEmail(svc, i)
time.Sleep(time.Second) // ただ1秒待つ
}
endFor := time.Now()
fmt.Printf("for実行時間: %f秒\n", (endFor.Sub(startFor)).Seconds())
}
func sendEmail(svc *ses.SES, i int) {
// Assemble the email.
input := &ses.SendEmailInput{
Destination: &ses.Destination{
CcAddresses: []*string{},
ToAddresses: []*string{
aws.String(Recipient),
},
},
Message: &ses.Message{
Body: &ses.Body{
Text: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Body + strconv.Itoa(i)),
},
},
Subject: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Subject),
},
},
Source: aws.String(Sender),
}
// Attempt to send the email.
result, err := svc.SendEmail(input)
// Display error messages if they occur.
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ses.ErrCodeMessageRejected:
fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
case ses.ErrCodeMailFromDomainNotVerifiedException:
fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
case ses.ErrCodeConfigurationSetDoesNotExistException:
fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}
実行結果
--- 省略 ---
{
MessageId: "0106017862e482c3-4921512d-e638-4dc1-aebb-b431b58c8b2b-000000"
}
for実行時間: 12.032761秒
実行結果はすべてMessageId
が返ってきて正常に送信できました。
送信先の受信トレイを確認するとテストメールが10通届きました。
送信レートを超えて送信してみる
短時間であれば最大送信レートの1通/秒を超えることができると説明されていました。
1通送信のたびに1秒待機するのはやめ、10通のメール送信を並行処理に変更します。
main.go
package main
import (
"fmt"
"strconv"
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
)
const (
Sender = "[your-ses-domain]"
Recipient = "[your-mailadress]"
Subject = "SES Test Mail"
Body = "Test mail count"
CharSet = "UTF-8"
)
func main() {
sess := session.Must(session.NewSession())
svc := ses.New(
sess,
aws.NewConfig().WithRegion("ap-northeast-1"))
var wg sync.WaitGroup
startGoroutine := time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
go sendEmail(svc, i, &wg)
}
wg.Wait()
endGotoutine := time.Now()
fmt.Printf("Goroutine実行時間: %f秒\n", (endGotoutine.Sub(startGoroutine)).Seconds())
}
func sendEmail(svc *ses.SES, i int, wg *sync.WaitGroup) {
defer wg.Done()
// Assemble the email.
input := &ses.SendEmailInput{
Destination: &ses.Destination{
CcAddresses: []*string{},
ToAddresses: []*string{
aws.String(Recipient),
},
},
Message: &ses.Message{
Body: &ses.Body{
Text: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Body + strconv.Itoa(i)),
},
},
Subject: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Subject),
},
},
Source: aws.String(Sender),
}
// Attempt to send the email.
result, err := svc.SendEmail(input)
// Display error messages if they occur.
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ses.ErrCodeMessageRejected:
fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
case ses.ErrCodeMailFromDomainNotVerifiedException:
fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
case ses.ErrCodeConfigurationSetDoesNotExistException:
fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}
実行結果
--- 省略 ---
{
MessageId: "0106017862edf429-963b71e1-a405-4d0a-8fb8-f0135d1670ae-000000"
}
Goroutine実行時間: 0.577789秒
実行結果は同様にすべてMessageId
が返ってきて1秒以内に10通送信できました。テストメールを10通受信しています。約0.6秒という短時間であれば最大送信レートを超過しても送信できました。
24時間以内の最大送信数
アカウントダッシュボードから確認できます。すでに51通送信していました。
aws ses
コマンドからも確認できます。
$ aws ses get-send-quota
{
"Max24HourSend": 200.0,
"MaxSendRate": 1.0,
"SentLast24Hours": 51.0
}
200通以上送信してみる
24時間以内200通制限を超過してみます。すでに51通送信しているため、あと150通で最大送信数を超過できます。
Goroutineの並行処理数を10
から200
に変更して実行します。
実行結果
--- 省略 ---
{
MessageId: "0106017862fe3d97-f29bf6fe-8d70-4f64-99aa-652e787d6061-000000"
}
Goroutine実行時間: 1.515848秒
実行結果はすべてMessageId
が返ってきました。最大送信数と最大送信レートに引っかかることなく約1.5秒で200通送信できました。201通目からエラーになると想定していたのですが本当に送信できたのでしょうか。
ダッシュボードから確認すると大幅に超過しています。
$ aws ses get-send-quota
{
"Max24HourSend": 200.0,
"MaxSendRate": 1.0,
"SentLast24Hours": 271.0
}
テストメールを200通受信しています。すべて受信するまでには時間がかかりました。
CloudWatchでSend
のメトリクスを確認しました。10分弱レートの値が1
を記録しています。最大送信レートはこの1
を指しているのではないかと疑問を持ちます。
調べてみると最大送信レートより早くSESを呼び出すとMaximum sending rate exceeded
が発生すると説明されていました。SESから送信されるメールの送信レートではありませんでした。
脱線してしまいました。話を戻します。
制限値を超過した状態からaws ses
コマンドで1通メールを送信してみました。Daily message quota exceeded.のエラーが返ってきました。
短時間で大量に送信し制限値を超えても急にメール送信停止にはなりませんでした。しかし、制限値を超えたことを確認した後に送信したくてもメール送信はできませんでした。
$ aws ses send-email \
--from [your-ses-domain] \
--to [your-mailadress] \
--subject test-subject \
--text test-body
実行結果
An error occurred (Throttling) when calling the SendEmail operation (reached max retries: 4): Daily message quota exceeded.
エラーメッセージまとめ
送信制限を超過時のエラーは下記の2種類あります。
エラーメッセージ | 説明 |
---|---|
Daily message quota exceeded | 24時間以内の最大送信数超過 |
Maximum sending rate exceeded | 秒間の最大送信レート超過 |
送信制限のエラー詳細
最大送信レート超過エラーも確認したい
秒間100通の最大瞬間風速的なアプローチではエラーは記録されませんでした。検証のため24時間空けて送信数を0通に戻しました。あらためて2通/秒のペースで1000通送信続けるアプローチを取ってみました。
実行結果は懐が深いようで1000通エラーなく処理されました。
1通も漏れずに1000通受信しています。
2通送信の並行処理に2秒かかることも稀にありました。反省点は安定して2通/秒をだせなかったことです。残念ですが時間の関係で打ち切ります。
送信制限のモニタリング
CloudWatchからSESのメトリクスを確認します。SendQuota
にあたる項目がありません。
Amazon SESコンソールか、Amazon SES APIで送信制限をモニタリングできますとドキュメントに書いてあります。CloudWatchでとは書いてないですね、そうでしたか。
取得してみた例
GoのSDKを使い24時間以内のメール送信数を取得してみます。
main.go
package main
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
)
func main() {
sess := session.Must(session.NewSession())
svc := ses.New(
sess,
aws.NewConfig().WithRegion("ap-northeast-1"))
input := &ses.GetSendQuotaInput{}
result, err := svc.GetSendQuota(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
default:
log.Fatal(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
log.Fatal(err.Error())
}
return
}
fmt.Printf("%g", *result.SentLast24Hours)
}
実行結果
271
ダッシュボードで確認した値と同じ値を取得できました。モニタリングするならCloudWatchへカスタムメトリクスとして定期的に送るよう仕込む必要があります。最大送信レートのモニタリングはエラー返ってきた時にアラート上げるしか方法が思いつかないです。なにかよい方法あればぜひ教えていただきたいです。
MackerelにはSent Last 24 Hours
の項目がありました。24時間以内の送信数を取得しています。
プラグインを確認するとGetSendQuota
で値取ってきているので上に載せたコードと同じ値を拾ってます。洗練されたコードを確認されたい方はMackerelエージェントのSESプラグインをご確認ください。
おわりに
最大送信レートは短時間であれば許されること知りました。あと、バウンス、苦情のメトリクスに目がいきがちで送信制限に関するメトリクスがないことを知りませんでした。ドキュメントには送信制限に近づいていないか確認することを推奨しますと書いてあるからCloudWatchの項目に追加されないかな、追加されたら嬉しいな。