πŸ‡―πŸ‡΅ ζ—₯本θͺž | πŸ‡ΊπŸ‡Έ English
Advertisement

SMTP Client in C

Apply what you learned about sockets to send email from your own program.

What is SMTP?

SMTP (Simple Mail Transfer Protocol) is the protocol used to send email. It uses port 25 (or 587).
Your program
(SMTP client)
SMTP
β†’
port 25
Mail server
(SMTP server)
β†’
Recipient's
mailbox
SMTP commandMeaningExample
HELOGreeting (announce our hostname)HELO myhost
MAIL FROM:Sender addressMAIL FROM:<sender@example.com>
RCPT TO:Recipient addressRCPT TO:<receiver@example.com>
DATABegin the message bodyDATA
.End the message body. (a line with only a period)
QUITEnd the sessionQUIT

SMTP Session Flow

The client and server literally "talk" in text. Server replies start with a 3-digit status code.
S: 220 mail.example.com SMTP Ready ← server greeting
C: HELO mycomputer ← client greeting
S: 250 Hello mycomputer
C: MAIL FROM:<alice@example.com> ← sender
S: 250 OK
C: RCPT TO:<bob@example.com> ← recipient
S: 250 OK
C: DATA ← begin body
S: 354 Start mail input
C: Subject: Test Mail
C:
C: Hello, this is a test.
C: . ← line with only a period ends the body
S: 250 OK
C: QUIT ← disconnect
S: 221 Bye
2xx = success
3xx = waiting for more input
4xx = temporary error
5xx = permanent error

Implementing It in C

Using the knowledge from the previous socket lesson, we'll build an SMTP client.

Helper Functions

First, a helper that makes sending and receiving easy.
#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

// send a command and print the server's response
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);
    }
}

// connect to an SMTP server
int connect_smtp(const char *host, int port) {
    struct hostent *h = gethostbyname(host);
    if (!h) { fprintf(stderr, "Host lookup failed\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;
}

Main Logic

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

    // 1. connect
    int sock = connect_smtp(smtp_host, 25);
    if (sock < 0) return 1;

    // 2. read greeting
    smtp_command(sock, NULL);

    // 3. SMTP session
    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. headers and body
    snprintf(cmd, BUF_SIZE,
        "Subject: Test from C program\r\n"
        "From: %s\r\n"
        "To: %s\r\n"
        "\r\n"                            // blank line ends headers
        "Hello!\r\n"
        "This mail was sent from a C program.\r\n"
        ".\r\n",                          // line with only a period ends the body
        from, to);
    smtp_command(sock, cmd);

    // 5. end session
    smtp_command(sock, "QUIT\r\n");

    close(sock);
    printf("\nMail sent.\n");
    return 0;
}
Note: Most providers block direct connections to port 25 today (known as OP25B). You may need a lab/test server, or to use port 587 with authentication.

Feature Extensions

Extension 1: Read the body from the keyboard

Use fgets to let the user type the body.
// read body from the keyboard
printf("Enter mail body (blank line to finish):\n");
char body[BUF_SIZE] = "";
char line[256];
while (1) {
    fgets(line, 256, stdin);
    if (line[0] == '\n') break;  // blank line to finish
    strcat(body, line);
}

Extension 2: Address book

Choose the recipient by number.
// address book
const char *addressbook[] = {
    "user1@example.com",
    "user2@example.com",
    "user3@example.com"
};
int num_addr = 3;

printf("Select recipient:\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];

Extension 3: Load body from a file

// read body from a text file
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);

Challenges

Challenge 1: Error handling for send/recv
Improve smtp_command to check server status codes (250, 354, etc.). If the response isn't expected, print an error and abort.
Challenge 2: Send to multiple recipients
Issuing RCPT TO multiple times sends one message to many addresses. Allow multi-selection from the address book.
Challenge 3: Subject from keyboard input
Let the user set the Subject header. Read it with fgets and insert it into the DATA payload.
Challenge 4: Log the session to a file
Add a feature that writes the full SMTP exchange to a text file β€” useful as evidence in lab reports.
Advanced: Encrypted Email
Write a program that encrypts the body before sending.
- Simple: Caesar cipher (shift each character by N)
- Intermediate: XOR cipher (XOR with a key string)
- Advanced: Implement public-key basics (modular exponentiation a^n mod p)
Also write a matching decrypt program on the receive side.
What This Series Covered
TCP/IP socket basics β†’ SMTP protocol β†’ email sender implementation β†’ feature extensions
← Back to Socket Programming
Share this article
Share on X