Send Email (SMTP and Resend)
Category: Backend & APIs
This page is generated from the Air Pipe marketplace. Browse it live to install into your organization.
One pack, two ways to send transactional email from an Air Pipe route — pick whichever fits your stack:
- SMTP (
smtp.yml) — Air Pipe's nativeemail:action speaks SMTP directly (via STARTTLS). Works with any mail server: Gmail, Fastmail, Postmark, Mailgun, Amazon SES SMTP, or your own relay. No SDK, self-hosted-friendly. - Resend (
resend.yml) — Air Pipe calls the Resend HTTP API. No SMTP server to run; just an API key and a verified domain.
Both routes take the same request shape — { to, subject, html } — so you can switch providers without changing your callers.
Which one should I use?
SMTP (smtp.yml) | Resend (resend.yml) | |
|---|---|---|
| How | Native email: action opens an SMTP connection | HTTP POST to api.resend.com |
| Best for | Existing mail infra, self-hosted, any provider | Fastest setup, no server to manage |
| You need | SMTP host + user + pass | A Resend API key + verified domain |
| Leaves your infra | Only the SMTP connection you configure | HTTPS call to Resend |
Deploy one file for real use — they're independent. Both ship here (distinct /email/smtp and /email/resend routes) so you can compare.
Endpoints
| Method | Route | Sends via | Body |
|---|---|---|---|
POST | /email/smtp | Native SMTP | { to, subject, html } |
POST | /email/resend | Resend API | { to, subject, html } |
Both validate the body (all three fields required → 400), send, and return { status: "sent", provider, … }. A delivery failure returns 502.
Setup
Add the managed variables for the option you're using (Air Pipe dashboard, or ap_vars self-hosted):
SMTP (smtp.yml):
| Name | Value |
|---|---|
SMTP_HOST | SMTP server hostname (e.g. smtp.postmarkapp.com) |
SMTP_USER | SMTP username |
SMTP_PASS | SMTP password / app password / API-key-as-password |
SMTP_FROM | Verified sender — plain or named (Acme <[email protected]>) |
The smtp: block defaults to port 587 + STARTTLS. For implicit TLS use port: 465; for an internal unencrypted relay use port: 25 + tls: false. port and tls are literal config values (not ap_vars), so edit them in smtp.yml directly.
Resend (resend.yml):
| Name | Value |
|---|---|
RESEND_API_KEY | Your Resend API key (starts with re_) |
RESEND_FROM | Verified sender (Acme <[email protected]>) |
Quick start
BASE=https://your-airpipe-host
# Send via SMTP
curl -sX POST $BASE/email/smtp -H 'content-type: application/json' -d '{
"to": "[email protected]",
"subject": "Welcome to Acme",
"html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>"
}'
# → { "to": "...", "subject": "...", "status": "sent", "provider": "smtp" }
# Send via Resend — identical body
curl -sX POST $BASE/email/resend -H 'content-type: application/json' -d '{
"to": "[email protected]",
"subject": "Welcome to Acme",
"html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>"
}'
# → { "id": "…", "status": "sent", "provider": "resend" }
Notes
- Same body, two providers — swap
smtp↔resendin the path; callers don't change. - Quotes & newlines are safe — the Resend route runs every user value through
->json_escape, so a"or newline in the subject/body can't break the JSON payload. - HTML email — both routes take an
htmlbody. (The native SMTP action also supports atext:field for a plaintext part; add it tosmtp.ymlif you want multipart.) - AWS SES — the native action reserves an
aws_sesoption, but SMTP and Resend are the two supported paths today; for SES, use its SMTP interface withsmtp.yml.
Configuration
resend.yml
name: SendEmailResend
description: Send transactional email via the Resend HTTP API — the managed, no-SMTP-server alternative. Air Pipe calls Resend's REST endpoint directly with your API key.
docs: true
# Resend (https://resend.com) is a developer-friendly email API: no SMTP server
# to run, just an API key and a verified sending domain. This is the managed
# option — Air Pipe POSTs to Resend over HTTPS instead of opening an SMTP socket.
#
# Free tier: 100 emails/day (3,000/month). Verify your domain in the Resend
# dashboard before sending from it.
#
# Required managed variables:
# RESEND_API_KEY — your Resend API key (starts with "re_")
# RESEND_FROM — verified sender, plain or named ("Acme <[email protected]>")
interfaces:
# POST /email/resend
# Body: { "to": "...", "subject": "...", "html": "<...>" }
email/resend:
output: http
method: POST
summary: Send email via Resend
description: Send an HTML email through the Resend API. Same request shape as the SMTP route.
tags: [email, resend]
request_example:
to: [email protected]
subject: Welcome to Acme
html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>"
response_example:
id: 4ef9a417-02e9-4d39-ad75-9611e0fcc33c
status: sent
provider: resend
actions:
- name: Validate
input: a|body|
hide_data_on_success: true
assert:
http_code_on_error: 400
error_message: "to, subject, and html are required"
tests:
- value: to
is_not_null: true
is_not_empty: true
description: Recipient email address.
- value: subject
is_not_null: true
is_not_empty: true
description: Email subject line.
- value: html
is_not_null: true
is_not_empty: true
description: HTML body of the email.
# Every user-supplied value goes through ->json_escape so a quote or newline
# in the subject/body can't break the JSON payload sent to Resend.
- name: SendResend
run_when_succeeded:
actions: [Validate]
http_code_on_error: 400
http:
url: https://api.resend.com/emails
method: POST
headers:
content-type: application/json
authorization: "Bearer a|ap_var::RESEND_API_KEY|"
body: |
{
"from": "a|ap_var::RESEND_FROM|",
"to": ["a|Validate::to->json_escape|"],
"subject": "a|Validate::subject->json_escape|",
"html": "a|Validate::html->json_escape|"
}
assert:
http_code_on_error: 502
error_message: "Resend rejected the email"
tests:
- value: status
is_equal_to: 200
- name: Result
run_when_succeeded: [SendResend]
input: a|SendResend|
post_transforms:
- extract_value: body
- add_attribute:
status: sent
provider: resend
smtp.yml
name: SendEmailSmtp
description: Send transactional email through any SMTP server (Gmail, Fastmail, Postmark, Mailgun, Amazon SES SMTP, or your own relay) using Air Pipe's native email action — no provider SDK, no HTTP client to wire up.
docs: true
# Air Pipe's `email:` action speaks SMTP directly, so you only need your
# provider's host + credentials — no SDK. This is the self-hosted-friendly
# option: any mail server works, and nothing leaves your infrastructure except
# the SMTP connection you configure.
#
# TLS / PORTS (set in the `smtp:` block below):
# 587 + tls: true → STARTTLS (recommended default — used here)
# 465 → implicit TLS / SMTPS
# 25 + tls: false → unencrypted (internal relays / testing only)
#
# Required managed variables:
# SMTP_HOST — SMTP server hostname (e.g. smtp.gmail.com, smtp.postmarkapp.com)
# SMTP_USER — SMTP username
# SMTP_PASS — SMTP password / app password / API-key-as-password
# SMTP_FROM — verified sender, plain or named ("Acme <[email protected]>")
interfaces:
# POST /email/smtp
# Body: { "to": "...", "subject": "...", "html": "<...>" }
email/smtp:
output: http
method: POST
summary: Send email via SMTP
description: Send an HTML email through your SMTP server using the native email action.
tags: [email, smtp]
request_example:
to: [email protected]
subject: Welcome to Acme
html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>"
response_example:
status: sent
provider: smtp
to: [email protected]
actions:
- name: Validate
input: a|body|
hide_data_on_success: true
assert:
http_code_on_error: 400
error_message: "to, subject, and html are required"
tests:
- value: to
is_not_null: true
is_not_empty: true
description: Recipient email address.
- value: subject
is_not_null: true
is_not_empty: true
description: Email subject line.
- value: html
is_not_null: true
is_not_empty: true
description: HTML body of the email.
# The native email action opens an SMTP connection and delivers the message.
# a|...| directives interpolate into the string fields (not into `port`/`tls`,
# which must stay literal because the config is typed).
- name: SendSmtp
run_when_succeeded:
actions: [Validate]
http_code_on_error: 400
email:
smtp:
server: a|ap_var::SMTP_HOST|
user: a|ap_var::SMTP_USER|
pass: a|ap_var::SMTP_PASS|
port: 587
tls: true
from: a|ap_var::SMTP_FROM|
to: a|Validate::to|
subject: a|Validate::subject|
html: a|Validate::html|
- name: Result
run_when_succeeded:
actions: [SendSmtp]
http_code_on_error: 502
input: a|Validate|
post_transforms:
- remove_keys: [html]
- add_attribute:
status: sent
provider: smtp