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

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 runs on port 25 (or 587 for submission).
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

Client and server literally "talk" in text. Each server reply starts with a three-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

Building on the previous socket lesson, let's build an SMTP client.

Helper Functions

First, a helper that makes sending a command and reading the response straightforward.
#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 now block direct connections to port 25 (a policy known as OP25B). You may need a lab/test server, or port 587 with authentication.

Feature Extensions

Extension 1: Read the body from the keyboard

Use fgets to let the user type the body interactively.
// 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

Pick 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 so it checks server status codes (250, 354, etc.). If the response is unexpected, print an error and abort.
Challenge 2: Send to multiple recipients
Issuing RCPT TO multiple times delivers one message to several 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 β€” handy 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 receiving side.
What this series covered
TCP/IP socket basics β†’ the SMTP protocol β†’ implementing an email sender β†’ feature extensions.
← Back to Socket Programming

Review Quiz

Check your understanding of this lesson!

Q1. What's the first command an SMTP client sends after connecting?

HELO or EHLO
MAIL FROM
RCPT TO

HELO/EHLO announces your hostname. EHLO is the extended form and also returns a list of supported features.

Q2. What's the standard port for plain (non-TLS) SMTP?

25
80
443

Port 25 is standard SMTP. Submission uses 587, and SMTPS typically uses 465.

Q3. Which sequence marks the end of the mail body in SMTP?

A line containing only a dot (\r\n.\r\n)
The literal string "END"
A null byte \0

After DATA, a line containing nothing but a period signals the end of the message body.

Share this article
Share on X