Minting image URLs in bulk
By Danish Mohammed, founder. 4 min read
A campaign snippet is one image address for the whole send. When each recipient needs something of their own in the image, such as a deadline or a separate evergreen clock, each recipient needs their own address. There are two ways to mint them in bulk, and both are on Growth and Agency, which pricing lists with their allowances.
The two routes
| Route | Input | Output | Ceiling |
|---|---|---|---|
POST /urls/batch |
A JSON list of items you build | JSON, in the same order | 10,000 items per call |
POST /recipient-sets/{setId}/urls |
A recipient list uploaded as CSV | A CSV, streamed | The whole list, up to 500,000 rows |
Minting needs only a key with read scope, because it changes nothing in the workspace. Creating a recipient list and uploading to it need write. Paths are relative to https://api.emailtimer.app/api/v1/workspaces/{workspaceId}.
The batch endpoint
Each item names a template and says what makes this address different. The fields are the same as a single mint:
| Field | What it does |
|---|---|
templateId or templatePublicId |
Exactly one of the two |
version |
Optional. Defaults to the version published now |
format |
gif or png |
recipientToken |
A token from a recipient list, carried as r |
params |
String values signed into the address, such as deadline |
watermark |
Optional. See the note on watermarks below |
curl https://api.emailtimer.app/api/v1/workspaces/$WORKSPACE_ID/urls/batch \
-H "Authorization: Bearer $EMAILTIMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "templatePublicId": "k7mQ2pXd", "params": { "deadline": "2026-12-01T09:00:00Z" } },
{ "templatePublicId": "k7mQ2pXd", "params": { "deadline": "2026-12-02T09:00:00Z" } }
]
}'
The answer is { "count": 2, "urls": [ ... ] }, and each entry carries url, templateId, publicId and version. The order matches the order of items, so join on position.
A call is all or nothing. If any item is unusable, nothing is minted and the call is refused, because a campaign where some recipients have no address sends some people no image. A malformed item is named in the refusal, as in items[3]. A template that cannot be found answers a plain 404, the same as a workspace you cannot see.
More than 10,000 items is refused with 413 and batch_too_large. An empty items array is refused with 400.
Splitting a large list
Split the list into chunks of at most 10,000 and send them in order. The batch route allows 600 requests a minute per key, measured as a token bucket that refills over 60 seconds. Every response carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy. A refused call is a 429 with Retry-After in seconds. Read those headers rather than keeping your own count.
Because a call is all or nothing, a retry of a refused chunk is safe. Minting the same inputs against the same version gives the same address, so a chunk sent twice produces identical addresses rather than new ones. Two things can still change the output between runs: a publish, so pin version, and the workspace crossing its plan, which adds the watermark flag described below.
The recipient list route
For a list that already lives in a spreadsheet, upload it once and export every address in one request.
POST /recipient-setswith{ "name": "Black Friday 2026" }. The answer carries the set’sid.POST /recipient-sets/{setId}/uploadwith the CSV as the body andContent-Type: text/csv.- Wait for the import.
GET /recipient-sets/{setId}reports its status and its row and error counts. POST /recipient-sets/{setId}/urlswith{ "templatePublicId": "k7mQ2pXd" }.
The upload is read in the background, up to 500,000 rows and 200 MB. Four column names are recipient fields: email, external_id, deadline and token. Every other column becomes a merge value that the template can draw. A deadline has to be an ISO timestamp with Z or an offset. A row that cannot be read is reported with its line number and the reason, and the rest of the file imports.
The export streams back as a CSV with the columns token,email,external_id,url. A key without write access gets it without email. Each address carries only the recipient’s token, so no name or email address appears in it. The image service looks up the row’s values from the token when the image is fetched, and keeps that record for two years so delivered emails keep rendering.
The export checks the template and version before it writes the first line. A template that was never published is refused with 409 before any row is written, rather than leaving you a half-written file.
Importing the addresses into your email platform
- Create a contact field in your platform to hold the address, such as
et_image_url. - Import the exported CSV, matching on email or on your own
external_id. - In the publish screen, choose the snippet called “A different image for each recipient”. Its image source is that field’s merge tag, in your platform’s syntax.
- Check that the field is filled for everyone in the segment before you send.
The publish screen gives the reason for step 4: a contact whose field is empty “gets no image at all”. Merge tag images lists the field syntax platform by platform.
A note on watermarks
An address minted while a workspace is past 110 percent of its plan carries wm=1 inside its signature. That watermark stays in the address for good, even after an upgrade, because the signature cannot change.
The image service applies the watermark at fetch time anyway while a workspace is over, and removes it once the workspace is back under. So an item can pass "watermark": false to mint a clean address. Recipient list exports do not take that option, so export after an upgrade rather than before. Plan quota and the watermark covers the rest.
Questions and answers
Is minting in bulk counted against my opens?
No. Opens are image fetches. An address costs nothing until a mail client fetches it.
Can a batch mix different templates?
Yes. Each item names its own template, and the service reads each distinct template once per call.
What happens if one item in a batch is wrong?
The whole call is refused and nothing is minted. Fix the item and send the chunk again.
Should I use the batch endpoint or a recipient list?
A recipient list when the data is a spreadsheet, or when personal values should stay out of the address. The batch endpoint when your own system builds each item, such as a deadline computed per order.
Related
Put a countdown in the campaign you are writing now
The free plan covers 5,000 opens a month with no card. Starter is $19 a month for 250,000.