Twilio SendGridライブラリで送信リクエストとレスポンスを取得する方法 ~Node.js編~
- 2025年7月24日
- by SendGrid
- Category: 技術ネタ 機能・使い方
メール送信リクエストを実行したのに、ActivityやEvent Webhookにイベントが記録されていない!という経験はありませんか?イベントが発生しない主な原因として、Twilio SendGridに送信リクエストが届いていない可能性が考えられます。問題が発生した際に原因をしっかり特定できるよう、実際に送ったJSON形式の送信リクエストとそのHTTPレスポンスをログに出力するよう心がけましょう。
SMTPやWeb APIで送信する場合は、TelnetやcURLなどのツールによって簡単に送信リクエストとレスポンスが取得できるので、チュートリアルをお試しください。今回のブログでは、Node.js向けのSendGridライブラリ(以降、sendgrid-nodejsと表記)における取得方法を紹介します。
今回は、Node.js 22.17.0で動作を確認しました。
sendgrid-nodejsを使った送信処理のサンプルコード
メールを送信するサンプルコードは次のとおりです。
app.js
import sgMail from '@sendgrid/mail'; import { convertMsgToRequestBody } from './convert_msg_to_request_body.mjs'; sgMail.setApiKey(process.env.APIKEY); const msg = { to: 'kozohanako@example.com', from: 'kozotaro@example.com', subject: 'Node.jsのライブラリを使ったメール送信', text: '構造さん\r\nお元気ですか。', html: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> \ <html xmlns="http://www.w3.org/1999/xhtml"> \ <head> \ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> \ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"> \ </head> \ <body> \ <p>構造さん</p> \ <p>お元気ですか。</p> \ </body> \ </html>', }; //送信リクエストを出力 const requestBody = convertMsgToRequestBody(msg); console.log(JSON.stringify(requestBody, null, 2)); (async () => { try { //メール送信とレスポンス出力 const [response] = await sgMail.send(msg); console.log('Status code:', response.statusCode); console.log('Headers:', response.headers); } catch (error) { console.error(error); if (error.response) { console.error(error.response.body); } } })();
convert_msg_to_request_body.mjs
export function convertMsgToRequestBody(msg) { const requestBody = { personalizations: [ { to: Array.isArray(msg.to) ? msg.to.map((email) => (typeof email === 'string' ? { email } : email)) : [{ email: msg.to }], subject: msg.subject, }, ], from: typeof msg.from === 'string' ? { email: msg.from } : msg.from, content: [], }; if (msg.text) { requestBody.content.push({ type: 'text/plain', value: msg.text }); } if (msg.html) { requestBody.content.push({ type: 'text/html', value: msg.html }); } return requestBody; }
環境変数を定義する.envファイルにはダッシュボードで作成したAPIキーを記述します。
APIKEY=SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
nodeコマンドでプログラムを実行します。
node --env-file=.env app.js
実行結果は次のとおりです。
{ "personalizations": [ { "to": [ { "email": “kozohanako@example.com" } ], "subject": "Node.jsのライブラリを使ったメール送信" } ], "from": { "email": "kozotaro@example.com" }, "content": [ { "type": "text/plain", "value": "構造さん\r\nお元気ですか。" }, { "type": "text/html", "value": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\"> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1\"> </head> <body> <p>構造さん</p> <p>お元気ですか。</p> </body> </html>" } ] } Status code: 202 Headers: Object [AxiosHeaders] { server: 'nginx', date: 'Mon, 21 Jul 2025 22:16:48 GMT', 'content-length': '0', connection: 'keep-alive', 'x-message-id': 'kA7AvuIBQOiBZGuXVwbWkQ', 'access-control-allow-origin': 'https://sendgrid.api-docs.io', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl', 'access-control-max-age': '600', 'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'content-security-policy': "frame-ancestors 'none'", 'cache-control': 'no-cache', 'x-content-type-options': 'no-sniff', 'referrer-policy': 'strict-origin-when-cross-origin' }
それでは、送信リクエストとレスポンスの取得部分を見てみましょう。
送信リクエストの取得方法
sendgrid-nodejsが生成した送信リクエストは外部からアクセスできない仕組みになっているため、convert_msg_to_request_body.mjsで実装した処理を使ってmsgからJSON形式のリクエストに変換する必要があります。
import { convertMsgToRequestBody } from './convert_msg_to_request_body.mjs'; //送信リクエストを出力 const requestBody = convertMsgToRequestBody(msg); console.log(JSON.stringify(requestBody, null, 2));
出力結果は次のとおりです。
{ "personalizations": [ { "to": [ { "email": "kozohanako@example.com" } ], "subject": "Node.jsのライブラリを使ったメール送信" } ], "from": { "email": "kozotaro@example.com" }, "content": [ { "type": "text/plain", "value": "構造さん\r\nお元気ですか。" }, { "type": "text/html", "value": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\"> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1\"> </head> <body> <p>構造さん</p> <p>お元気ですか。</p> </body> </html>" } ] }
続いて、レスポンスの取得方法を紹介します。
レスポンスの取得方法
レスポンスはsgMail.send(msg)の戻り値から取得します。
(async () => { try { //メール送信とレスポンス出力 const [response] = await sgMail.send(msg); console.log('Status code:', response.statusCode); console.log('Headers:', response.headers); } catch (error) { console.error(error); if (error.response) { console.error(error.response.body); } } })();
次のような応答が返ります。
正常応答の場合
Status code: 202 Headers: Object [AxiosHeaders] { server: 'nginx', date: 'Mon, 21 Jul 2025 22:16:48 GMT', 'content-length': '0', connection: 'keep-alive', 'x-message-id': 'kA7AvuIBQOiBZGuXVwbWkQ', 'access-control-allow-origin': 'https://sendgrid.api-docs.io', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl', 'access-control-max-age': '600', 'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'content-security-policy': "frame-ancestors 'none'", 'cache-control': 'no-cache', 'x-content-type-options': 'no-sniff', 'referrer-policy': 'strict-origin-when-cross-origin' }
エラ-の場合(正しくないメールアドレス形式を指定した場合)
code: 400, response: { headers: Object [AxiosHeaders] { server: 'nginx', date: 'Mon, 21 Jul 2025 22:33:06 GMT', 'content-type': 'application/json', 'content-length': '204', connection: 'keep-alive', 'access-control-allow-origin': 'https://sendgrid.api-docs.io', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl', 'access-control-max-age': '600', 'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'content-security-policy': "frame-ancestors 'none'", 'cache-control': 'no-cache', 'x-content-type-options': 'no-sniff', 'referrer-policy': 'strict-origin-when-cross-origin' }, body: { errors: [Array] } } } { errors: [ { message: 'Does not contain a valid address.', field: 'personalizations.0.to.0.email', help: 'http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.personalizations.to' } ] }
エラ-の場合(無効な認証情報が指定された場合)
code: 401, response: { headers: Object [AxiosHeaders] { server: 'nginx', date: 'Mon, 21 Jul 2025 22:34:02 GMT', 'content-type': 'application/json', 'content-length': '116', connection: 'keep-alive', 'access-control-allow-origin': 'https://sendgrid.api-docs.io', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl', 'access-control-max-age': '600', 'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html', 'strict-transport-security': 'max-age=31536000; includeSubDomains', 'content-security-policy': "frame-ancestors 'none'", 'cache-control': 'no-cache', 'x-content-type-options': 'no-sniff', 'referrer-policy': 'strict-origin-when-cross-origin' }, body: { errors: [Array] } } } { errors: [ { message: 'The provided authorization grant is invalid, expired, or revoked', field: null, help: null } ] }
レスポンスがエラーの場合は、送信リクエストに誤りがないかどうか確認しましょう。また、リクエストは正常に受け付けられたがメールへの差し込みがうまくいかない、指定したテンプレートが反映されないなど、お困りの場合は、弊社サポートチームがフォローいたします。送信リクエストとレスポンス、受信メールを添えてお問い合わせください。
コードの再確認をしましょう
SendGridでは送信後の状況をActivityやEvent Webhookで確認できるため、その手前の送信リクエストやレスポンスのログ出力を忘れがちです。今回のブログをきっかけに、しっかりログ出力できているかご自身のコードをもう一度見直してみてください。