「C#/Windows Forms で Gmail に SMTP 接続したら例外が出て送れない」。これは 2025 年現在でも最頻の相談です。原因の大半は、Google が“通常のパスワードによる基本認証”を強力にブロックしているため。この記事では、動く設定(短期)と、将来も安心な構成(長期)を、エラーの読み解き方から実装コード、運用まで実務目線で徹底解説します。
C#/Windows Forms で Gmail 送信時に起きる代表的な問題と背景
Gmail は「ユーザー名+パスワード」の基本認証を段階的に無効化してきました。結果として、.NET 標準の System.Net.Mail.SmtpClient に通常パスワードを渡すだけでは、多くの場合 認証失敗(5xx) や 暗号化必須(STARTTLS 要求) のエラーで弾かれます。MailKit を使った場合でも、XOAUTH2(OAuth 2.0)でアクセストークンを渡さない限り同様に失敗します。
例外の多くは次のいずれかに該当します。
- 535 5.7.8 Username and Password not accepted:通常パスワードによる認証拒否。
- 534 5.7.9 Application-specific password required:アプリパスワードが必要。
- 530 5.7.0 Must issue a STARTTLS command first:暗号化未開始(587 で STARTTLS を要求)。
- 5.7.1 “From” not authorized:送信者アドレスがアカウントに紐づく「送信元として許可」されていない。
これらは設定で回避できます。短期は「アプリ パスワード」、長期は「OAuth 2.0 + MailKit または Gmail API」への移行がベストプラクティスです。
質問の再掲と要件整理
SmtpClientでsmtp.gmail.com(ポート 465)+通常パスワード → 例外で送信不可。- MailKit でもユーザー名/パスワードの基本認証では送信不可。
- どう設定すれば Gmail から送れるのか、推奨構成は何か。
結論:状況別ベストプラクティス早見表
| 目的 | 推奨構成 | 補足 |
|---|---|---|
| 手早く動かしたい(個人開発・検証) | 1) Google アカウントで2 要素認証(2FA)を有効化 2) アプリ パスワードを発行(16 桁) 3) SmtpClient で smtp.gmail.com / 587 / EnableSsl=true4) NetworkCredential("あなたの Gmail", "アプリ パスワード") を使用 | 587 は STARTTLS(TLS)用。465(SSL/TLS 即時)でも動く場合あり。ただし一般には 587 を推奨。 将来停止のリスクを考えると本番運用は避け、検証・個人用途に限定。 |
| 将来も運用したい(業務・商用) | OAuth 2.0 + MailKit(XOAUTH2) へ移行。 Google Cloud Console で OAuth クライアントを作成し、アクセストークンで認証。 | Google は基本認証を厳格に制限。OAuth 2.0 が安全で持続可能。 個人 Gmail/Workspace どちらでも採用可。 |
| Google Workspace(独自ドメイン) | 送信量・可用性を重視するなら SMTP リレー(smtp-relay.gmail.com) の検討。 もしくは Gmail API で直接送信。 | リレーは IP 許可や認証方式を管理側で制御可能。 Gmail API は細粒度の権限制御・厳密な監査が可能。 |
短期解:アプリ パスワード + SmtpClient(最小構成)
手順(概要)
- Google アカウントで 2FA を有効化。
- セキュリティ設定から「アプリ パスワード」を新規作成(アプリ=メール、端末=Windows など任意)。
- 発行された 16 文字のコードを 通常パスワードの代わりに 使用。
- SMTP は 587 + STARTTLS を使用(
EnableSsl = true)。
最小コード例(.NET / Windows Forms 共通)
using System;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
public static class GmailSmtpSample
{
public static async Task SendWithAppPasswordAsync(string to, string subject, string html)
{
var from = "あなたのメールアドレス@gmail.com";
using var msg = new MailMessage(from, to, subject, html) { IsBodyHtml = true };
// .NET Framework (<4.7) を使う場合は TLS1.2 を明示(古い OS/ランタイム対策)
// System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using var smtp = new SmtpClient("smtp.gmail.com", 587)
{
EnableSsl = true,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Credentials = new NetworkCredential(from, "ここに16桁のアプリパスワード")
};
// WinForms の UI スレッドをブロックしない
await smtp.SendMailAsync(msg);
}
}
つまずきポイントと対処
- 465 で失敗:
EnableSsl=trueでも失敗する構成があります。まずは 587 + STARTTLS で確認。 - 「From」が別ドメイン:Gmail 側で「送信元として許可」済みかを確認(Gmail 設定の「他のアドレスから送信」)。
- 2FA 未有効:アプリ パスワード自体が発行できません。先に 2FA を有効化。
- Workspace 組織ポリシー:管理者がアプリ パスワードを禁止しているケースあり。運用方針を確認。
長期解:MailKit + OAuth 2.0(XOAUTH2)
本番運用・将来の安心を重視するなら OAuth 2.0 による XOAUTH2 認証が最適です。リフレッシュトークンにより長期運用が可能で、パスワード保管が不要、権限を最小化できます。
準備(要点)
- Google Cloud Console で「OAuth クライアント ID」を作成(種類:デスクトップアプリ推奨)。
- クライアント ID/シークレットを取得。
- スコープに
https://mail.google.com/を指定してユーザー認可(初回のみブラウザで許可)。 - 取得したリフレッシュトークンを安全に保管(DPAPI/Windows 資格情報マネージャなど)。
コード例:トークン取得(初回認可)
using Google.Apis.Auth.OAuth2;
using Google.Apis.Util.Store;
using System.Threading;
using System.Threading.Tasks;
public static class GoogleAuthHelper
{
public static async Task AuthorizeAsync(string clientId, string clientSecret, string userEmail)
{
var secrets = new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret };
var scopes = new[] { "[https://mail.google.com/](https://mail.google.com/)" };
// token.json に暗号化して保存(サンプル。実運用は DPAPI 等で保護)
return await GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets, scopes, userEmail, CancellationToken.None, new FileDataStore("token.json", true));
}
}
コード例:MailKit で XOAUTH2 認証して送信
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using System.Threading.Tasks;
public static class MailKitGmailSender
{
public static async Task SendAsync(string userEmail, string to, string subject, string html,
string clientId, string clientSecret)
{
var cred = await GoogleAuthHelper.AuthorizeAsync(clientId, clientSecret, userEmail);
var accessToken = await cred.GetAccessTokenForRequestAsync();
var message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(userEmail));
message.To.Add(MailboxAddress.Parse(to));
message.Subject = subject;
message.Body = new TextPart("html") { Text = html };
using var smtp = new SmtpClient();
// 587 + STARTTLS を推奨
await smtp.ConnectAsync("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
var oauth2 = new MailKit.Security.SaslMechanismOAuth2(userEmail, accessToken);
await smtp.AuthenticateAsync(oauth2);
await smtp.SendAsync(message);
await smtp.DisconnectAsync(true);
}
}
運用ヒント
- トークンの自動更新:
UserCredentialは期限前に自動更新します。更新失敗時は再認可 UI を案内。 - 権限の最小化:スコープの付与は必要最小限に。
- ログ:MailKit の
ProtocolLoggerを使うと SMTP のやり取りをファイル出力でき、障害解析が容易です。
ProtocolLogger サンプル
using var logger = new MailKit.ProtocolLogger("smtp.log");
using var smtp = new SmtpClient(logger);
// ... Connect/Auth/Send ...
Workspace の選択肢:SMTP リレー/Gmail API
SMTP リレー(smtp-relay.gmail.com)
組織のゲートウェイとして SMTP リレーを用いると、IP 許可や認証方式、送信制限を管理者が一元制御できます。クライアント側の資格情報管理を減らせる点がメリット。既存の SMTP 実装を活かしつつ可用性とスケーラビリティを確保しやすくなります。
Gmail API 送信
SMTP を介さず、HTTP 経由で Gmail API に直接「メッセージ送信」を行う方式です。権限・監査・スロットリング制御が明確で、クラウドネイティブな実装に向きます。
Gmail API(C#)最小例
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using MimeKit;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
public static class GmailApiSender
{
public static async Task SendAsync(string clientId, string clientSecret, string userEmail,
string to, string subject, string html)
{
var cred = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret },
new[] { GmailService.Scope.GmailSend },
userEmail, CancellationToken.None);
using var svc = new GmailService(new BaseClientService.Initializer
{
HttpClientInitializer = cred,
ApplicationName = "GmailApiSenderSample"
});
var mime = new MimeMessage();
mime.From.Add(MailboxAddress.Parse(userEmail));
mime.To.Add(MailboxAddress.Parse(to));
mime.Subject = subject;
mime.Body = new TextPart("html") { Text = html };
using var stream = new MemoryStream();
await mime.WriteToAsync(stream);
var raw = Convert.ToBase64String(stream.ToArray())
.Replace('+', '-').Replace('/', '_').TrimEnd('=');
var request = svc.Users.Messages.Send(new Message { Raw = raw }, "me");
await request.ExecuteAsync();
}
}
エラー別トラブルシュート集(保存版)
| エラー(例) | 原因 | 対処 |
|---|---|---|
535 5.7.8 Username and Password not accepted | 通常パスワードによる認証が拒否。 | 2FA を有効化しアプリ パスワードを使用、または OAuth 2.0 へ移行。 |
534 5.7.9 Application-specific password required | アプリ パスワード必須のアカウント状態。 | アプリ パスワードを発行して差し替え。 |
530 5.7.0 Must issue a STARTTLS command first | TLS 未開始での認証試行。 | ポート 587 にして EnableSsl=true(SmtpClient)/StartTls(MailKit)。 |
5.7.1 From not authorized/550 5.7.1 | From アドレスが送信許可されていない。 | Gmail の「他のアドレスから送信」で当該 From を追加・確認済みに。 |
System.Security.Authentication.AuthenticationException | TLS ネゴシエーション失敗(古い TLS/中間証明書など)。 | OS/.NET を更新。古い .NET は Tls12 を強制。企業プロキシの証明書挿入も確認。 |
MailKit.Security.SslHandshakeException | 同上。MailKit の TLS ハンドシェイク失敗。 | 最新 TLS を有効化。プロキシや検疫アプライアンスの影響を調査。 |
Rate limit/Daily user sending limit exceeded | 送信上限超過。 | 送信ペース制御、キューイング、Workspace でのリレーや API 活用を検討。 |
Attachment rejected | 禁止拡張子やマルウェア検知。 | ZIP 暗号化は不可。安全な拡張子へ変更/クラウド共有に切り替え。 |
Windows Forms 実装の落とし穴と回避策
UI フリーズを避ける
同期 Send は UI スレッドを止めます。SendMailAsync も内部実装の都合でブロック感が出ることがあるため、MailKit の SendAsync+await を推奨。キャンセルは CancellationToken を渡せる設計に。
キャンセル対応の雛形
using System.Threading;
using System.Threading.Tasks;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
public class WinFormsMailer
{
public async Task SendAsync(string from, string to, string subject, string html, string accessToken, CancellationToken ct)
{
var msg = new MimeMessage();
msg.From.Add(MailboxAddress.Parse(from));
msg.To.Add(MailboxAddress.Parse(to));
msg.Subject = subject;
msg.Body = new TextPart("html") { Text = html };
using var smtp = new SmtpClient();
await smtp.ConnectAsync("smtp.gmail.com", 587, SecureSocketOptions.StartTls, ct);
await smtp.AuthenticateAsync(new MailKit.Security.SaslMechanismOAuth2(from, accessToken), ct);
await smtp.SendAsync(msg, ct);
await smtp.DisconnectAsync(true, ct);
}
}
再試行とバックオフ
ネットワークやレート制限で一時的に失敗する場合があります。指数バックオフ(例:1s→2s→4s… 最大 30s)で 3~5 回程度まで再試行する設計が現実的です。永続的な 5xx(権限・ポリシー違反)は即座にユーザーへガイダンスを表示します。
資格情報の安全な保管(Windows 向け実装)
平文での保存やソースコード直書きは厳禁。Windows 環境なら DPAPI(ProtectedData)や資格情報マネージャの利用が簡便です。
DPAPI(ユーザー スコープ)例
using System;
using System.Security.Cryptography;
using System.Text;
public static class SecretStore
{
public static string Protect(string plain)
{
var bytes = Encoding.UTF8.GetBytes(plain);
var enc = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(enc);
}
public static string Unprotect(string cipher)
{
var bytes = Convert.FromBase64String(cipher);
var dec = ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(dec);
}
}
アプリ パスワードやリフレッシュトークンは上記のように暗号化して保存し、利用時に復号しましょう。組織利用ではさらに OS の資格情報マネージャや秘密情報保管庫を検討します。
日本語・HTML・添付ファイルの取り扱い(MailKit 推奨)
日本語の件名・本文・ファイル名の MIME エンコードは、MimeKit に任せるのが確実です。複数パート(HTML + プレーンテキスト)や添付も容易。
日本語+代替本文+添付ファイル
using MimeKit;
using MailKit.Net.Smtp;
using MailKit.Security;
using System.Threading.Tasks;
public static class RichMailSample
{
public static async Task SendAsync(string from, string to, string accessToken)
{
var builder = new BodyBuilder
{
HtmlBody = "こんにちは。
HTML 本文です。",
TextBody = "こんにちは。\r\nプレーンテキストの本文です。"
};
builder.Attachments.Add("見積書.pdf"); // 日本語ファイル名も自動エンコード
var msg = new MimeMessage();
msg.From.Add(new MailboxAddress("営業部 山田", from));
msg.To.Add(MailboxAddress.Parse(to));
msg.Subject = "【見積送付】4 月度ご提案";
msg.Body = builder.ToMessageBody();
using var smtp = new SmtpClient();
await smtp.ConnectAsync("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
await smtp.AuthenticateAsync(new MailKit.Security.SaslMechanismOAuth2(from, accessToken));
await smtp.SendAsync(msg);
await smtp.DisconnectAsync(true);
}
}
送信ドメイン整合性(SPF/DKIM/DMARC)と「From」運用
Gmail から @gmail.com で送る分には Gmail 側の DKIM 等が整っていますが、From を独自ドメインに切り替える場合は、Gmail の「他のアドレスから送信」で当該アドレスを認証し、独自ドメイン側で SPF/DKIM/DMARC を正しく設定してください。未整備だと、受信側で迷惑判定やなりすまし警告の原因になります。
チェックリスト(導入順)
- まず 587 + STARTTLS で接続できるか確認(Firewall/プロキシ経由も含めて許可)。
- 個人検証なら 2FA 有効化 → アプリ パスワード を発行し、SmtpClient で送信可否をテスト。
- 本番運用は MailKit + OAuth 2.0 へ移行(トークン保管・自動更新・再認可フローを組み込む)。
- Workspace かつ大規模なら SMTP リレー or Gmail API を検討。
- 「From」の権限・SPF/DKIM/DMARC を整備。添付ポリシーも合わせて運用設計。
- 失敗時は プロトコルログ と 例外メッセージ で切り分け(5xx は恒久、4xx は一時と判断)。
よくある質問(FAQ)
Q. アプリ パスワードでずっと運用して良い?
A. 将来の仕様変更リスクを避けるため、本番は OAuth 2.0 を推奨します。アプリ パスワードは検証・臨時用途にとどめましょう。
Q. 465(SSL/TLS 即時)と 587(STARTTLS)はどちらが良い?
A. 一般には 587 + STARTTLS を推奨します。ネットワーク機器やプロキシとの相性が良いケースが多く、障害切り分けもしやすいからです。
Q. SmtpClient はまだ使える?
A. 互換性のため残っているものの、モダンな認証・機能性の観点から MailKit への移行が安全です。
Q. 送信が急に失敗しはじめた
A. レート制限・添付ポリシー変更・From 許可の失効・トークン有効期限・ネットワーク機器の証明書更新など、環境側の変化を疑いましょう。プロトコルログでの一次切り分けが有効です。
サンプル完全版:Windows Forms 連携(OAuth 2.0 + MailKit)
ボタンを押して送信、結果を UI に反映、キャンセルも可能な最小構成サンプルです。
/* フォーム側(Send ボタンのクリック) */
private CancellationTokenSource _cts;
private async void btnSend_Click(object sender, EventArgs e)
{
using (_cts = new CancellationTokenSource())
{
btnSend.Enabled = false;
btnCancel.Enabled = true;
lblStatus.Text = "送信中...";
try
{
var clientId = txtClientId.Text.Trim();
var clientSecret = txtClientSecret.Text.Trim();
var from = txtFrom.Text.Trim();
var to = txtTo.Text.Trim();
await MailKitGmailSender.SendAsync(
userEmail: from,
to: to,
subject: txtSubject.Text,
html: txtBodyHtml.Text,
clientId: clientId,
clientSecret: clientSecret
);
lblStatus.Text = "送信完了";
}
catch (OperationCanceledException)
{
lblStatus.Text = "中止しました";
}
catch (Exception ex)
{
lblStatus.Text = "失敗: " + ex.Message;
}
finally
{
btnSend.Enabled = true;
btnCancel.Enabled = false;
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
上記は概念実装です。実運用では(1)プロトコルログの採取、(2)再試行ポリシー、(3)UI スレッドとログ出力のデッドロック回避、(4)資格情報の安全保管、を取り入れてください。
運用のベストプラクティスまとめ
- 短期はアプリ パスワード+587/STARTTLSで手早く検証。
- 長期は OAuth 2.0(MailKit/Gmail API)でアクセストークン運用。
- From の権限とドメイン整合性(SPF/DKIM/DMARC)を整える。
- UI 非同期化+キャンセル+ログで使い勝手と保守性を両立。
- 秘密情報は暗号化保存(DPAPI/資格情報マネージャ/Vault)。
基本認証はブロックされる前提で設計しましょう。Gmail 側のセキュリティ要件に寄り添い、モダンな認証に移行することが、トラブルの少ない運用への最短距離です。
参考:SmtpClient でのエラーハンドリング例(検証用途)
try
{
await GmailSmtpSample.SendWithAppPasswordAsync("[email protected]", "テスト送信", "<b>Hello</b>");
}
catch (SmtpException ex)
{
// SMTP レベルのエラー(ステータスコードや応答メッセージを記録)
// 535 / 530 / 550 ... の応答をログへ
Console.Error.WriteLine($"SMTP error: {ex.StatusCode} {ex.Message}");
}
catch (System.Security.Authentication.AuthenticationException ex)
{
// TLS ハンドシェイクの失敗(証明書・TLS バージョン)
Console.Error.WriteLine("TLS error: " + ex.Message);
}
catch (Exception ex)
{
// その他のネットワーク・タイムアウト等
Console.Error.WriteLine(ex);
}
参考:ポート/暗号化/認証の対応表(覚えておくと楽)
| 項目 | 推奨値 | ポイント |
|---|---|---|
| SMTP サーバー | smtp.gmail.com | Workspace の SMTP リレーは smtp-relay.gmail.com。 |
| ポート | 587 | STARTTLS で暗号化開始。465 は SSL/TLS 即時。 |
| 暗号化 | TLS(必須) | 「Must issue a STARTTLS…」は暗号化未開始のサイン。 |
| 認証 | OAuth 2.0(XOAUTH2) | 長期運用はトークンベース一択。検証ならアプリ パスワード。 |
| From | 認可済みアドレス | 未認可の From は 5.7.1 系で拒否/書き換え。 |
| 添付 | 安全な拡張子 | 実行ファイル類はブロック。暗号 ZIP も不可。 |
最後に:実装戦略の指針
「とにかく今すぐ送りたい」ならアプリ パスワードで成功体験を作り、並行して OAuth 2.0 へ置き換えましょう。MailKit なら日本語・添付・マルチパートも含めハンドリングが容易で、WinForms との相性も良好です。エラーはメッセージを素直に読み、表のとおりに切り分ければ必ず原因に辿り着けます。今日から“動く”だけでなく“続く”メール送信へ。

コメント