ZapSMTP / Docs / Modules, images & timersResponse
Upload an image
POST/v1/template-assets
Upload an image for use in templates, as a multipart file or by URL; returns its public URL.
Requires a full-access live key; test keys get 403 test_key_restricted.
PNG, JPEG, GIF or WebP, up to 5 MB and 40 megapixels; the file content decides the type, not its name. PNG and JPEG images wider than 1,200 px are scaled down to 1,200 px (twice a 600 px email); GIF (animations included) and WebP are kept as they are. width and height are those of the stored image.
The URL is public (anyone with it can load the image) and long-lived: use it in <img src> of any template. The name keeps letters, digits, spaces and . _ ( ) -, up to 200 characters.
Each account holds up to 2,000 images and 1 GB.
Headers
Content-Typestringrequiredmultipart/form-datawith the image in fieldfile, orapplication/jsonwith the body below.
Body parameters
urlstringrequired- JSON upload: an http(s) URL we download the image from (private and internal addresses are refused). Limits: unreachable, not HTTP 200 or not a URL → 422 invalid_parameter
namestring- JSON upload: the file name shown in the image library. Default
the last segment of the URL.
Errors
restricted_api_keyHTTP 403test_key_restrictedHTTP 403- test-mode keys can send test email and read, not change the account: use a live key
payload_too_largeHTTP 413- images are limited to 5 MB
invalid_imageHTTP 422- only PNG, JPEG, GIF and WebP images are accepted | image dimensions are too large | images are limited to 5 MB | the image could not be decoded
invalid_parameterHTTP 422- send the image as multipart field "file", or JSON {"url": "..."} | url must be an http(s) URL | fetching url returned HTTP <n> | could not fetch url: <reason> | the image at url is over 5 MB
quota_exceededHTTP 422- image library is full (2,000 images or 1 GB): delete unused images first
storage_unavailableHTTP 503- object storage is not configured
curl -X POST 'https://zapsmtp.xyz/v1/template-assets' \
-H "Authorization: Bearer $ZAP_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"url": "https://acme.com/brand/logo.png",
"name": "logo.png"
}'const res = await fetch('https://zapsmtp.xyz/v1/template-assets', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ZAP_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://acme.com/brand/logo.png",
"name": "logo.png"
}),
});
// 204 No Content (e.g. a delete) has no body
const data = res.status === 204 ? null : await res.json();
if (!res.ok) throw new Error(`${data.name}: ${data.message}`);
console.log(data);import os
import requests
res = requests.post(
"https://zapsmtp.xyz/v1/template-assets",
headers={"Authorization": f"Bearer {os.environ['ZAP_API_KEY']}"},
json={
"url": "https://acme.com/brand/logo.png",
"name": "logo.png",
},
timeout=30,
)
res.raise_for_status()
print(res.json() if res.status_code != 204 else "deleted")<?php
$ch = curl_init('https://zapsmtp.xyz/v1/template-assets');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ZAP_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://acme.com/brand/logo.png',
'name' => 'logo.png',
]),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException($data['message']);
}
print_r($data);package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{
"url": "https://acme.com/brand/logo.png",
"name": "logo.png"
}`)
req, err := http.NewRequest(
"POST",
"https://zapsmtp.xyz/v1/template-assets",
body,
)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("ZAP_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}require 'net/http'
require 'json'
uri = URI('https://zapsmtp.xyz/v1/template-assets')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{ENV.fetch('ZAP_API_KEY')}"
req['Content-Type'] = 'application/json'
req.body = {
url: "https://acme.com/brand/logo.png",
name: "logo.png"
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, (JSON.parse(res.body) unless res.body.to_s.empty?)import java.net.URI;
import java.net.http.*;
public class ZapRequest {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(URI.create("https://zapsmtp.xyz/v1/template-assets"))
.header("Authorization", "Bearer " + System.getenv("ZAP_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"url": "https://acme.com/brand/logo.png",
"name": "logo.png"
}"""))
.build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}using System.Net.Http.Headers;
using System.Text;
using var http = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://zapsmtp.xyz/v1/template-assets");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("ZAP_API_KEY"));
request.Content = new StringContent("""
{
"url": "https://acme.com/brand/logo.png",
"name": "logo.png"
}
""", Encoding.UTF8, "application/json");
var response = await http.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");Set
ZAP_API_KEY in your environment.Response 201 Created
{
"id": "5b0e3c1a-9d2f-4e7b-8a61-2f4c6d8e0a13",
"name": "logo.png",
"ext": "png",
"content_type": "image/png",
"size": 18342,
"width": 480,
"height": 120,
"created_at": "2026-10-22T16:10:00Z",
"object": "template_asset",
"url": "https://zapsmtp.xyz/i/5b0e3c1a-9d2f-4e7b-8a61-2f4c6d8e0a13.png"
}