🇯🇵 日本語 | 🇺🇸 English
広告スペース

C言語でSMTPメールクライアントを作る

ソケット通信を応用して、プログラムからメールを送信する。

SMTPとは

SMTP(Simple Mail Transfer Protocol)は、メールを送信するためのプロトコル(通信規約)です。ポート番号は25(または587)を使います。
あなたのプログラム
(SMTPクライアント)
SMTP

ポート25
メールサーバー
(SMTPサーバー)
受信者の
メールボックス
SMTPコマンド意味
HELO挨拶(自分のホスト名を通知)HELO myhost
MAIL FROM:送信元のメールアドレスMAIL FROM:<sender@example.com>
RCPT TO:宛先のメールアドレスRCPT TO:<receiver@example.com>
DATAメール本文の開始DATA
.メール本文の終了.(ピリオドのみの行)
QUITセッション終了QUIT

SMTPセッションの流れ

クライアントとサーバーがテキストで「会話」します。サーバーの応答は数字3桁のステータスコードで始まります。
S: 220 mail.example.com SMTP Ready ← サーバーの挨拶
C: HELO mycomputer ← クライアントの挨拶
S: 250 Hello mycomputer
C: MAIL FROM:<alice@example.com> ← 送信元
S: 250 OK
C: RCPT TO:<bob@example.com> ← 宛先
S: 250 OK
C: DATA ← 本文開始
S: 354 Start mail input
C: Subject: Test Mail
C:
C: Hello, this is a test.
C: . ← ピリオドのみの行で本文終了
S: 250 OK
C: QUIT ← 切断
S: 221 Bye
2xx = 成功
3xx = 続きの入力を待っている
4xx = 一時的エラー
5xx = 永続的エラー

C言語で実装する

前回のソケット通信の知識を使って、SMTPクライアントを実装します。

ヘルパー関数

まず、送受信を簡単にするヘルパー関数を作ります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>

#define BUF_SIZE 4096

// サーバーにコマンドを送信し、応答を受け取って表示する
void smtp_command(int sock, const char *cmd) {
    char buf[BUF_SIZE];
    if (cmd != NULL) {
        send(sock, cmd, strlen(cmd), 0);
        printf("C: %s", cmd);
    }
    int n = recv(sock, buf, BUF_SIZE - 1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("S: %s", buf);
    }
}

// SMTPサーバーに接続する
int connect_smtp(const char *host, int port) {
    struct hostent *h = gethostbyname(host);
    if (!h) { fprintf(stderr, "ホスト解決失敗\n"); return -1; }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) { perror("socket"); return -1; }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    memcpy(&addr.sin_addr.s_addr, h->h_addr, h->h_length);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect"); close(sock); return -1;
    }
    return sock;
}

メイン処理

int main(void) {
    const char *smtp_host = "mail.example.com";
    const char *from = "alice@example.com";
    const char *to   = "bob@example.com";

    // 1. SMTPサーバーに接続
    int sock = connect_smtp(smtp_host, 25);
    if (sock < 0) return 1;

    // 2. サーバーの挨拶を受信
    smtp_command(sock, NULL);

    // 3. SMTPセッション
    smtp_command(sock, "HELO mycomputer\r\n");

    char cmd[BUF_SIZE];

    snprintf(cmd, BUF_SIZE, "MAIL FROM:<%s>\r\n", from);
    smtp_command(sock, cmd);

    snprintf(cmd, BUF_SIZE, "RCPT TO:<%s>\r\n", to);
    smtp_command(sock, cmd);

    smtp_command(sock, "DATA\r\n");

    // 4. メールヘッダと本文
    snprintf(cmd, BUF_SIZE,
        "Subject: Test from C program\r\n"
        "From: %s\r\n"
        "To: %s\r\n"
        "\r\n"                            // 空行でヘッダ終了
        "Hello!\r\n"
        "This mail was sent from a C program.\r\n"
        ".\r\n",                          // ピリオドのみの行で本文終了
        from, to);
    smtp_command(sock, cmd);

    // 5. セッション終了
    smtp_command(sock, "QUIT\r\n");

    close(sock);
    printf("\nメール送信完了\n");
    return 0;
}
注意: 現在多くのメールサーバーはポート25への直接接続を制限しています(OP25B)。実験環境のサーバーや、ポート587+認証を使う必要がある場合があります。

拡張機能の実装

拡張1: メール本文のキーボード入力

fgets でユーザーからメール本文を入力させる。
// メール本文をキーボードから入力
printf("メール本文を入力(空行で終了):\n");
char body[BUF_SIZE] = "";
char line[256];
while (1) {
    fgets(line, 256, stdin);
    if (line[0] == '\n') break;  // 空行で終了
    strcat(body, line);
}

拡張2: アドレス帳機能

番号を選んで送り先を切り替える。
// アドレス帳
const char *addressbook[] = {
    "user1@example.com",
    "user2@example.com",
    "user3@example.com"
};
int num_addr = 3;

printf("送り先を選択:\n");
for (int i = 0; i < num_addr; i++)
    printf("  %d: %s\n", i + 1, addressbook[i]);

int choice;
scanf("%d", &choice);
const char *to = addressbook[choice - 1];

拡張3: ファイルから本文を読み込む

// テキストファイルからメール本文を読み込む
FILE *fp = fopen("message.txt", "r");
if (fp == NULL) { perror("fopen"); return 1; }

char body[BUF_SIZE] = "";
char line[256];
while (fgets(line, 256, fp) != NULL)
    strcat(body, line);
fclose(fp);

チャレンジ課題

課題1: 送受信のエラーハンドリング
smtp_command 関数を改良し、サーバーの応答コード(250, 354等)をチェックする。期待しないコードが返ってきたらエラーメッセージを表示して中断する。
課題2: 複数宛先への一斉送信
RCPT TOコマンドを複数回送ることで、1通のメールを複数の宛先に同時に送れる。アドレス帳から複数選択できるようにせよ。
課題3: 件名をキーボードから入力
Subject ヘッダをユーザー入力で設定できるようにせよ。fgets でSubjectを入力し、DATAコマンド以降に組み込む。
課題4: メール送信のログをファイルに保存
送受信のやりとり(SMTPセッション)をテキストファイルに記録する機能を追加せよ。レポート作成時の証拠として使える。
発展課題: 暗号メールの実装
メール本文を暗号化してから送信するプログラムを作成せよ。
・簡易版: シーザー暗号(各文字をN文字ずらす)で暗号化/復号
・応用版: XOR暗号(鍵文字列とのXOR演算)
・上級版: 公開鍵暗号の原理(べき剰余計算 a^n mod p)を実装
暗号化した本文をメールで送り、受信側で復号するプログラムも作成する。
このシリーズで学べたこと
TCP/IPソケット通信の基礎 → SMTPプロトコルの理解 → メール送信プログラムの実装 → 拡張機能の設計
← ソケット通信入門に戻る
この記事をシェア
X(Twitter)でシェアはてブ

ネットワークプログラミングを深めるなら

ソケット通信の基礎を学んだら書籍で体系的に

📘
苦しんで覚えるC言語
MMGames 著
初心者向けの定番入門書。
Amazonで見る
📗
新・明解C言語 入門編
柴田望洋 著
図解が豊富で演習問題も充実。
Amazonで見る
📙
プログラミング言語C 第2版
B.W.カーニハン, D.M.リッチー 著
通称K&R。C言語の原典。
Amazonで見る

※ 上記リンクはアフィリエイトリンクです。購入によりサイト運営を支援いただけます。