Application Developers schicken regelmäßig E-Mails aus ihren Anwendungen heraus — Bestellbestätigungen, Passwort-Resets, Benachrichtigungen. Aber die meisten Developer haben nie gelernt, was unter der Haube passiert.
Was SMTP eigentlich ist
SMTP (Simple Mail Transfer Protocol, RFC 5321) ist ein 1982 standardisiertes Protokoll, das definiert wie E-Mails übertragen werden. Es ist textbasiert und hat sich seit den 1980ern erstaunlich wenig geändert.
S: 220 mail.example.com ESMTP Postfix
C: EHLO client.ihr-unternehmen.de
S: 250-mail.example.com Hello
S: 250-STARTTLS
S: 250 AUTH LOGIN PLAIN
C: STARTTLS
S: 220 Ready to start TLS
[TLS handshake]
C: EHLO client.ihr-unternehmen.de
C: AUTH PLAIN [base64-encoded credentials]
S: 235 Authentication successful
C: MAIL FROM: <noreply@ihr-unternehmen.de>
S: 250 OK
C: RCPT TO: <empfaenger@example.com>
S: 250 OK
C: DATA
S: 354 Send message
C: From: noreply@ihr-unternehmen.de
C: Subject: Test
C:
C: Hallo Welt
C: .
S: 250 Message accepted (queued as ABCD1234)
C: QUIT
Status-Codes: 2xx = success, 3xx = continue, 4xx = temporary failure (retry), 5xx = permanent failure.
Warum Status-Codes wichtig sind
2xx (Success): der Mail-Server hat die Mail akzeptiert. Das bedeutet NICHT, dass sie beim Empfänger angekommen ist — nur, dass der nächste MTA in der Kette sie übernommen hat.
4xx (Temporary failure): Server kann die Mail jetzt nicht akzeptieren, später wieder versuchen. Beispiele: 4.7.0 (Server überlastet), 4.7.1 (Rate-Limit), 4.2.1 (Greylisting).
5xx (Permanent failure): Mail wird nicht akzeptiert. Beispiele: 5.1.1 (Mailbox existiert nicht), 5.1.2 (Empfänger-Domain ungültig), 5.7.1 (Spam-Klassifikation). Empfänger sollte aus aktiven Listen entfernt werden.
Code-Beispiel: Python mit smtplib
import smtplib
from email.message import EmailMessage
from email.utils import formataddr
def send_order_confirmation(recipient_email, order_id, total_amount):
msg = EmailMessage()
msg['From'] = formataddr(('Ihr Unternehmen', 'noreply@ihr-unternehmen.de'))
msg['To'] = recipient_email
msg['Subject'] = f'Bestellbestätigung #{order_id}'
msg['Reply-To'] = 'support@ihr-unternehmen.de'
msg.set_content(f"Vielen Dank für Ihre Bestellung #{order_id}.\nBetrag: {total_amount} EUR")
try:
with smtplib.SMTP_SSL('smtp.authorizehosting.com', 465, timeout=10) as smtp:
smtp.login('your_username', 'your_password')
smtp.send_message(msg)
return True
except smtplib.SMTPRecipientsRefused as e:
# 5xx - permanent
suppression_list.add(recipient_email)
return False
except (smtplib.SMTPServerDisconnected, ConnectionError) as e:
# Netzwerk - retry
retry_queue.add(msg)
return False
Code-Beispiel: Node.js mit nodemailer
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'smtp.authorizehosting.com',
port: 465,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
},
pool: true,
maxConnections: 5,
maxMessages: 100,
rateLimit: 10
});
async function sendOrderConfirmation(recipientEmail, orderId, totalAmount) {
try {
const info = await transporter.sendMail({
from: '"Ihr Unternehmen" <noreply@ihr-unternehmen.de>',
to: recipientEmail,
replyTo: 'support@ihr-unternehmen.de',
subject: `Bestellbestätigung #${orderId}`,
text: `Vielen Dank für Ihre Bestellung #${orderId}. Betrag: ${totalAmount} EUR`
});
return true;
} catch (error) {
if (error.responseCode >= 500) {
await suppressionList.add(recipientEmail);
} else if (error.responseCode >= 400) {
await retryQueue.add({ recipientEmail, orderId, totalAmount });
}
return false;
}
}
Code-Beispiel: Java mit Spring Boot
# application.properties
spring.mail.host=smtp.authorizehosting.com
spring.mail.port=587
spring.mail.username=${SMTP_USERNAME}
spring.mail.password=${SMTP_PASSWORD}
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.connectiontimeout=10000
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
public boolean sendOrderConfirmation(String recipientEmail, Long orderId) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("noreply@ihr-unternehmen.de");
message.setTo(recipientEmail);
message.setSubject(String.format("Bestellbestätigung #%d", orderId));
message.setText(String.format("Vielen Dank für Ihre Bestellung #%d", orderId));
try {
mailSender.send(message);
return true;
} catch (MailSendException e) {
log.error("Mail send failed", e);
return false;
}
}
}
Häufige Developer-Fehler
Fehler 1: Synchroner Versand im Request-Handler. Mail-Versand kann Sekunden dauern. Niemals im Request-Response-Zyklus blockieren — immer asynchron via Queue.
Fehler 2: Keine Retry-Logik. 4xx-Fehler werden ignoriert, Mail geht verloren. Exponential backoff für 4xx, kein Retry für 5xx.
Fehler 3: Connection nicht wiederverwenden. Bei Hochvolumen Connection-Pooling nutzen.
Fehler 4: Hardcoded Credentials. SMTP-Username/Passwort im Code-Repository ist Sicherheits-Risiko. Environment-Variables oder Secret-Manager nutzen.
Fehler 5: Kein Reply-To. Empfänger-Antworten landen in einem Postfach, das niemand überwacht.
Fehler 6: HTML ohne Plain-Text-Alternative. Manche Spam-Filter werten HTML-only Mails negativer.
Fehler 7: Keine Suppression-Verarbeitung. Empfänger mit Hard-Bounce wird wieder versendet.
Wann SMTP, wann Email API?
Für Standard-Anwendungsfälle reicht SMTP. Email API ist strukturell überlegen bei: Webhook-Events (Open, Click, Bounce in Echtzeit), Idempotency-Garantien, detailliertes Tracking pro Mail, Template-Management auf Anbieter-Seite.
Bei Authorize Hosting unterstützen wir beide ohne Mehrkosten.
SMTP-Setup für Ihre Anwendung professionell aufsetzen?
Wir prüfen Ihre SMTP-Integration und schlagen Verbesserungen vor — Connection-Pooling, Retry-Logik, Suppression-Verarbeitung.