تخطَّ إلى المستندات
C#

client.Templates

كل دالّة في مساحة الأسماء هذه: توقيعها ومعلماتها وما تُرجعه ومثال عليها.

الدوالّ

Bodies stored once and sent many times, with versioned drafts, previews, declared props and slots, AI design and per-template analytics.

Templates.ListAsync

List templates, most recently updated first

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
Task<Page> ListAsync(    string? status = null,    string? search = null,    string? sort = null,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns one page of the workspace's templates, ordered by updatedAt descending. Each row carries the published version's subject, engine, slots and props, so you can see what a send needs without fetching every template. A template with nothing published omits those four fields and reports publishedVersion as null.

Paging is keyset on updatedAt, and the cursor is opaque: it holds the sort and where the last template on the page sat in it, so a template deleted while you page never breaks the walk. Editing a template moves it to the front, so a template changed while you page can show up twice. Pass nextCursor back as cursor: while hasMore is true, or let ListAllAsync or IterateAsync walk the pages for you.

status: narrows to one template status. draft is the status of a template created without publish and never published or activated since. It does not mean "has unpublished edits": check latestVersion against publishedVersion for that.

المعلمات

statusstring?

Restricts the page to draft, active or archived templates. Omit it for all of them.

searchstring?

Matches the name, the slug, the description, the published version's subject and the id, each word loosely, with close spellings when nothing matches exactly, as a substring. % and _ are taken literally.

sortstring?

The order: updated-newest (the default), updated-oldest, created-newest, created-oldest, name or name-reversed. The cursor follows whichever you asked for.

limitint?

Rows per page, a whole number from 1 to 100. The server defaults to 25.

cursorstring?

The nextCursor from the previous page. Never build one yourself.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A Page with items, hasMore and nextCursor. Each template has id, name, slug, description, status, publishedVersion, latestVersion, createdAt and updatedAt, plus subject, engine, slots and props when a version is published.

مثال

var page = await client.Templates.ListAsync(status: "active", limit: 50); foreach (var template in page){    Console.WriteLine($"{template["slug"]} published v{template["publishedVersion"]?.ToString() ?? "-"}, latest v{template["latestVersion"]}");} if (page.HasMore){    var next = await client.Templates.ListAsync(status: "active", limit: 50, cursor: page.NextCursor);     Console.WriteLine($"The next page starts with {next.Items[0]?["slug"]?.ToString() ?? "nothing"}");}

ملاحظات

  • A cursor is only valid under the sort it was handed out with. Keep sort and search the same while you page, which ListAllAsync and IterateAsync do for you.

  • A cursor this list did not hand out, or one handed out under another sort, is a 400 invalid_cursor, not an empty page. Start again without a cursor.

  • publishedVersion lower than latestVersion means the body was edited after the last publish, and live sends still use the published version.

  • publishedVersion is null for a template that has never been published, here and on GetAsync, CreateAsync and UpdateAsync alike.

متاح أيضًا في

API
GET /templates
TypeScript
templates.list()
Python
templates.list()
Ruby
templates.list
PHP
templates->list
Go
Templates.List
Java
templates().list
CLI
openemail templates list

Templates.ListAllAsync

Collect every template into one object

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
Task<IReadOnlyList<JsonObject>> ListAllAsync(    string? status = null,    string? search = null,    string? sort = null,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Walks every page of the template list and returns all of them in a single object, most recently updated first. It follows nextCursor until hasMore is false, so the number of requests is the template count divided by the page size.

A workspace holds at most 200 templates, so passing limit: 100 normally finishes in two requests. status: collects only one status. Paging is keyset on updatedAt, so a template edited by someone else during the walk can appear twice: de-duplicate on id if other writers are active.

المعلمات

statusstring?

Collects only draft, active or archived templates.

searchstring?

Matches the name, the slug, the description, the published version's subject and the id, each word loosely.

sortstring?

The order to walk in: updated-newest (the default), updated-oldest, created-newest, created-oldest, name or name-reversed.

limitint?

Page size for each request, 1 to 100. The server defaults to 25.

cursorstring?

Starts the walk from this cursor instead of the first page.

apiKeystring?

Overrides the client API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A list of JsonObject items holding every matching template, each with id, slug, status, publishedVersion and latestVersion, plus the published subject, engine, slots and props where one exists.

مثال

var templates = await client.Templates.ListAllAsync(limit: 100); Console.WriteLine(templates.Count);

ملاحظات

  • Each page is a separate request. If one fails the call throws and the pages already fetched are discarded.

  • Archived templates count towards the 200 limit, and only DeleteAsync frees a place.

  • Use IterateAsync when you only need the first match, since it stops fetching once you break.

متاح أيضًا في

API
GET /templates
TypeScript
templates.listAll()
Python
templates.list_all()
Ruby
templates.list_all
PHP
templates->listAll
Go
Templates.ListAll
Java
templates().listAll

Templates.IterateAsync

Stream templates one at a time

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
IAsyncEnumerable<JsonObject> IterateAsync(    string? status = null,    string? search = null,    string? sort = null,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns an IAsyncEnumerable<JsonObject> that yields templates individually and requests the next page only after the current one is drained. Nothing is fetched until you start consuming it, and breaking out of the loop stops further requests, so this is the cheapest way to find one template by a property the API cannot filter on.

The walk follows nextCursor and ends when hasMore is false, when a page carries no nextCursor, or when the server repeats a cursor. Order is most recently updated first, so a template edited while you iterate can be yielded twice. Collect ids during the loop and act on them afterwards if you are also writing.

المعلمات

statusstring?

Yields only draft, active or archived templates.

searchstring?

Matches the name, the slug, the description, the published version's subject and the id, each word loosely.

sortstring?

The order to walk in: updated-newest (the default), updated-oldest, created-newest, created-oldest, name or name-reversed.

limitint?

Page size per request, 1 to 100. The server defaults to 25.

cursorstring?

Starts the walk from this cursor instead of the first page.

apiKeystring?

Overrides the client API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

An IAsyncEnumerable<JsonObject> that yields one template per step.

مثال

await foreach (var template in client.Templates.IterateAsync(status: "archived")){    if ((string?)template["name"] == "Order shipped")    {        Console.WriteLine($"{template["id"]} {template["slug"]} v{template["latestVersion"]}");         break;    }}

ملاحظات

  • The generator is lazy, so an abandoned loop costs only the pages it consumed.

متاح أيضًا في

API
GET /templates
TypeScript
templates.iterate()
Python
templates.iterate()
Ruby
templates.iterate
PHP
templates->iterate
Go
Templates.Iterate
Java
templates().iterate

Templates.GetAsync

Read a template with its head version in full

الصلاحياتtemplates:read
التوقيع
Task<JsonObject> GetAsync(    string idOrSlug,    string? apiKey = null,    CancellationToken cancellationToken = default)

Fetches one template by tpl_ id or by slug. Every templates method accepts either, and the two cannot collide because ids carry the tpl_ prefix while a slug has no underscores. Pin the slug in code: it is derived once at creation and never changes when the template is renamed.

latest is the head version, meaning the current draft, or the published version when nothing has been edited since. This is the only read that returns the body: document for the blocks engine, and html for the html engine exactly as submitted, before sanitising. The top level subject, engine, slots and props describe the published version, which is what a send uses, so they differ from latest while somebody has unpublished edits.

When nothing has been published yet, publishedVersion is null and the top level subject, engine, slots and props are absent. Read the draft from latest instead. Use ListAsync or ListVersionsAsync to find out whether a template can actually be sent.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the fields of a ListAsync row plus latest, an object for the head version with id, version, state, engine, subject, slots, props, document, html, publishedAt and createdAt.

مثال

var template = await client.Templates.GetAsync("order-shipped");var head = template["latest"]; Console.WriteLine($"{template["id"]} live v{template["publishedVersion"]?.ToString() ?? "none"}, head v{head?["version"]} {head?["state"]}");

ملاحظات

  • A missing template is a 404 resource_not_found, whether it never existed or belongs to another workspace.

  • Read required props from the top level props, not from a preview. A preview tolerates a missing required prop and a send refuses it.

  • The html of latest is the markup as you posted it. What goes out is the sanitised copy compiled at publish, which PreviewAsync shows.

متاح أيضًا في

API
GET /templates/{id}
TypeScript
templates.get()
Python
templates.get()
Ruby
templates.get
PHP
templates->get
Go
Templates.Get
Java
templates().get
CLI
openemail templates get

Templates.CreateAsync

Create a template and its first version

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> CreateAsync(    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

Creates the template together with version 1. Without ["publish"] = true that version is a draft, and a draft cannot be sent: SendAsync answers 422 template_not_published until somebody calls publish. With ["publish"] = true the body is compiled straight away and the template starts active, so a body that fails to render is refused here instead of later.

The engine follows what you send. Posting html selects the html engine, anything else is blocks. A blocks template stores a document whose body is a tree of @react-email/components nodes (Section, Row, Column, Container, Text, Heading, Button, Link, Img, Hr, Markdown, CodeBlock and CodeInline), validated on the way in with a ceiling of 500 nodes nested 8 deep. An html template stores markup you rendered yourself, for example with @react-email/render in your own build, and it is sanitised when the version is compiled.

A placeholder is a key between double braces, and it is filled from declared slots and props. A slot has a default and belongs to whoever edits the template. A prop is supplied by the sender and can be required. Keys start with a letter and continue with letters, digits or underscores, and one key cannot be both. In a blocks template an undeclared placeholder is a 422 invalid_template naming its path. In html markup, any placeholder used inside an attribute such as href or src must be declared with kind url or image, or compiling fails.

المعلمات

namestringمطلوب

Display name, 1 to 100 characters after trimming and unique per workspace.

slugstring

Stable handle of lowercase letters, digits and hyphens, at most 64 characters. Derived from name when omitted.

descriptionstring

Free text note, at most 500 characters.

publishbool

Compiles and publishes version 1 immediately. Defaults to false, which leaves a draft.

starterstring

A starter slug from ListStartersAsync, which seeds the subject and the body. Anything you send yourself wins over the starter, and an unknown slug is a 404.

enginedictionary

blocks or html. Inferred from whether html is present.

subjectstring

Subject line with optional placeholders, each a key between double braces, at most 998 characters and free of line breaks.

documentdictionary

The blocks body: body (the block tree) plus the page settings that travel with it, preview, tailwind, fonts and style. Defaults to an empty body.

htmlstring

Pre-rendered markup for the html engine. Required for it, at most 1,000,000 characters.

slotslist

Up to 100 editor filled values, each with key, optional label, kind (default text) and default (default empty string).

propslist

Up to 100 sender supplied values, each with key, optional label, kind (default text), required (default false) and default (default null).

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the new id and slug, status of draft or active, latestVersion of 1, and version 1's subject, engine, slots and props.

مثال

var template = await client.Templates.CreateAsync(new Body{    ["name"] = "Order shipped",    ["slug"] = "order-shipped",    ["engine"] = "html",    ["subject"] = "Order {{orderId}} is on its way",    ["html"] = "<p>Hi {{customer}},</p><p>Order {{orderId}} has shipped. <a href=\"{{trackingUrl}}\">Track it</a>.</p>",    ["props"] = new[]    {        new Body { ["key"] = "orderId", ["required"] = true },        new Body { ["key"] = "customer", ["default"] = "there" },        new Body { ["key"] = "trackingUrl", ["kind"] = "url", ["required"] = true },    },    ["publish"] = true,}); Console.WriteLine($"{template["id"]} {template["status"]}");

ملاحظات

  • Without publish, the response reports publishedVersion as null and leaves out subject, engine, slots and props, because nothing is published yet.

  • A duplicate name is a 409 template_name_taken and a duplicate slug is a 409 template_slug_taken, both scoped to the workspace.

  • A workspace holds at most 200 templates, archived ones included. Creating one more is a 422 workspace_limit_reached.

  • Not retried by the SDK, since there is no idempotency key on this route. After a lost response, GetAsync the slug before trying again.

متاح أيضًا في

API
POST /templates
TypeScript
templates.create()
Python
templates.create()
Ruby
templates.create
PHP
templates->create
Go
Templates.Create
Java
templates().create
CLI
openemail templates create

Templates.UpdateAsync

Edit template metadata or its draft body

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> UpdateAsync(    string idOrSlug,    IReadOnlyDictionary<string, object?> patch,    string? apiKey = null,    CancellationToken cancellationToken = default)

Changes a template in place. name, description and status are metadata and never create a version. Sending any of subject, document, html, slots, props or engine is a body edit: if the head version is still a draft it is overwritten, and if the head is published a new draft numbered one higher is minted. Body fields you leave out keep the head's values, so a patch carrying only subject keeps the existing document and declarations, while slots or props replace the whole list.

Nothing here changes what a live send returns. Sends keep using the published version until you call PublishAsync, which is what makes it safe to edit a template in production. The response's latest is the version this call wrote into.

Pass expectedVersion with the latestVersion you read before editing. If another writer has moved the head since, the call fails with 409 version_conflict instead of overwriting their change. Without it the last write wins. Setting ["status"] = "archived" is the reversible alternative to delete: an archived template refuses to send with 422 template_archived until its status is active again.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

namestring

Replacement name, 1 to 100 characters and unique per workspace. The slug does not follow it.

slugstring

Replacement slug of lowercase letters, digits and hyphens, at most 64 characters. Anything pinning the old slug starts getting 404s, and taking another template's slug is a 409 template_slug_taken.

descriptionstring

Replacement note of at most 500 characters, or null to clear it.

statusstring

Archives or reactivates the template: archived or active. It cannot be set back to draft.

expectedVersionint

The head version number you edited from. A mismatch is a 409 version_conflict and nothing is written.

enginedictionary

Switches between blocks and html. Switching to html needs html supplied or already stored.

subjectstring

Replacement subject, at most 998 characters and free of line breaks.

documentdictionary

The blocks body: body (the block tree) plus the page settings that travel with it, preview, tailwind, fonts and style. It replaces the whole document, so read the current one before rebuilding part of it. Revalidated in full.

htmlstring

Replacement markup for the html engine, at most 1,000,000 characters.

slotslist

Complete replacement slot list.

propslist

Complete replacement prop list.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the saved metadata, the published version's fields at the top level, and latest set to the version this edit wrote into, including its document or html.

مثال

var current = await client.Templates.GetAsync("order-shipped"); var updated = await client.Templates.UpdateAsync("order-shipped", new Body{    ["subject"] = "Your order {{orderId}} has shipped",    ["expectedVersion"] = current["latestVersion"],}); Console.WriteLine($"Draft v{updated["latest"]?["version"]} is {updated["latest"]?["state"]}"); await client.Templates.PublishAsync("order-shipped");

ملاحظات

  • Not retried by the SDK. With expectedVersion set, repeating the call yourself after a lost response is a 409 if the first attempt minted a version.

  • An archived template refuses to send with 422 template_archived. PublishAsync sets status back to active, so publishing an archived template reactivates it.

  • The merged body is validated as a whole, so an edit can be refused with 422 invalid_template for what it does to fields you did not send.

  • Renaming onto another template's name is a 409 template_name_taken.

متاح أيضًا في

API
PATCH /templates/{id}
TypeScript
templates.update()
Python
templates.update()
Ruby
templates.update
PHP
templates->update
Go
Templates.Update
Java
templates().update
CLI
openemail templates update

Templates.DuplicateAsync

Copy a template into a new one

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> DuplicateAsync(    string idOrSlug,    IReadOnlyDictionary<string, object?>? body = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Creates a new template from the HEAD version of an existing one: the same subject, body, slots and props, and the source's description. The copy starts at version 1 as a DRAFT, whatever the source had published, so a copy is never sendable by accident. Publish it when you mean to.

The copy is a separate template with its own id and slug. Nothing links it back to the source, so editing either one afterwards leaves the other alone. This is the safe way to try a redesign of a template that is sending in production.

name is optional. Left out, the source name is reused, and because a name is unique per workspace the server appends a number until one is free, up to twenty attempts. Sending a name that is already taken behaves the same way, so a script that copies nightly keeps working.

المعلمات

idOrSlugstringمطلوب

The template to copy, by tpl_ id or slug.

namestring

The name for the copy, 1 to 100 characters. Omit it to reuse the source name with a number appended.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject for the NEW template, with latest set to its version 1 draft, including the copied document or html. The server answers 201.

مثال

var copy = await client.Templates.DuplicateAsync("order-shipped", body: new Body { ["name"] = "Order shipped, new design" }); Console.WriteLine($"{copy["id"]} {copy["slug"]} v{copy["latestVersion"]}"); await client.Templates.UpdateAsync(copy["id"]!.GetValue<string>(), new Body { ["subject"] = "Your order is on the way" });

ملاحظات

  • The copy takes the source's HEAD, which is the unpublished draft when there is one. Duplicate after publishing if you want the live body.

  • Not retried by the SDK. Calling it twice makes two copies, each with its own numbered name.

  • A workspace at its 200 template limit is a 422 workspace_limit_reached, and nothing is copied.

  • Every name from the base to the base plus 20 being taken is a 409 template_name_taken.

متاح أيضًا في

API
POST /templates/{id}/duplicate
TypeScript
templates.duplicate()
Python
templates.duplicate()
Ruby
templates.duplicate
PHP
templates->duplicate
Go
Templates.Duplicate
Java
templates().duplicate
CLI
openemail templates duplicate

Templates.ReplaceContentAsync

Swap a template's design for a starter or another template's

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> ReplaceContentAsync(    string idOrSlug,    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

Replaces the whole body of a template with a starter design or with another template's body, keeping the template's own identity: its id, slug, name, description and status do not move, and neither does what a live send returns.

The write lands exactly where an UpdateAsync to the body lands. A draft head is overwritten in place; a published head mints version N+1 as a draft. Sends keep resolving the published version until you publish, so this is safe to call on a template that is sending.

Name exactly one source. starter takes a slug from ListStartersAsync; fromTemplateId takes another template in the same workspace, whose published version is copied when it has one and whose draft is copied otherwise. Sending both, neither, or the target itself is a 422. A source in another workspace is a 404, like everything else here.

This discards the body it replaces. A version that was published is still in the version list and can be restored, but an unpublished draft body is gone.

المعلمات

idOrSlugstringمطلوب

The template whose design is being replaced, by tpl_ id or slug.

starterstring

A starter slug from ListStartersAsync. Mutually exclusive with fromTemplateId.

fromTemplateIdstring

Another template in this workspace to borrow the design from, by id or slug. Mutually exclusive with starter.

expectedVersionint

The head version you read before replacing. A mismatch is a 409 version_conflict and nothing is written.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the unchanged metadata and latest set to the version this call wrote into, carrying the new document or html.

مثال

var current = await client.Templates.GetAsync("order-shipped"); var replaced = await client.Templates.ReplaceContentAsync("order-shipped", new Body { ["starter"] = "order-shipped", ["expectedVersion"] = current["latestVersion"] }); Console.WriteLine($"Draft v{replaced["latest"]?["version"]} is {replaced["latest"]?["state"]}"); await client.Templates.PublishAsync("order-shipped");

ملاحظات

  • The new body brings the source's slots and props with it, replacing the target's declarations entirely. A send that passed the old props may start failing with unknown_template_prop, so read the response before publishing.

  • A template cannot replace its own design: that is a 422 invalid_template on fromTemplateId.

  • Not retried by the SDK. With expectedVersion set, a repeat after a lost response is a 409 if the first attempt landed.

متاح أيضًا في

API
POST /templates/{id}/content
TypeScript
templates.replaceContent()
Python
templates.replace_content()
Ruby
templates.replace_content
PHP
templates->replaceContent
Go
Templates.ReplaceContent
Java
templates().replaceContent
CLI
openemail templates replace-content

Templates.DeleteAsync

Delete a template and every version

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> DeleteAsync(    string idOrSlug,    string? apiKey = null,    CancellationToken cancellationToken = default)

Permanently removes the template and all of its versions. There is no undo. Mail already accepted is unaffected, because each send stores the body it rendered, but any integration still sending against this id or slug starts receiving 404s.

If you might need the template again, archive it with UpdateAsync(idOrSlug, new Body { ["status"] = "archived" }) instead. The response is a tombstone rather than an empty body, so a log line can record exactly what was removed.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with object set to template, the id and deleted set to true, where id is always the tpl_ id, even when you passed a slug.

مثال

try{    var deleted = await client.Templates.DeleteAsync("order-shipped");     Console.WriteLine($"{deleted["id"]} deleted");}catch (OpenEmailApiException error) when (error.IsConflict){    Console.WriteLine($"Not deleted: {error.Code}");}

ملاحظات

  • Deleting frees both the name and the slug. A template created later with the same slug gets a new tpl_ id.

  • Archived templates still count towards the 200 template limit, so deleting is the only way to make room.

  • Refused with a 409 template_in_use while a scheduled or queued broadcast still names the template. Cancel the broadcast or wait until it starts sending.

  • Not retried by the SDK. Repeating a delete that already succeeded is a 404.

متاح أيضًا في

API
DELETE /templates/{id}
TypeScript
templates.delete()
Python
templates.delete()
Ruby
templates.delete
PHP
templates->delete
Go
Templates.Delete
Java
templates().delete
CLI
openemail templates delete

Templates.ListVersionsAsync

List one page of a template's versions, newest first

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
Task<Page> ListVersionsAsync(    string idOrSlug,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns one page of a template's versions, newest first. Every publish adds a version, so a template edited for a long time holds many: follow nextCursor while hasMore is true to reach version 1, or let ListAllVersionsAsync and IterateVersionsAsync do that walk. At most one version is a draft and it is always the highest number. Every version below it has been published, and the highest published version is the one unpinned sends use.

Bodies are left out, so document and html are absent on every row. What each version declares (subject, slots and props) is present, which makes this the call for checking what pinning a version on SendAsync would commit you to.

Pages are keyset on the version number, so a version deleted while you walk never breaks the walk: the next page starts at the first version below the cursor.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

limitint?

Versions per page, a whole number from 1 to 100. The server defaults to 25.

cursorstring?

The nextCursor of the previous page, passed back as it came. It is opaque, so never build one yourself.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A Page with items, hasMore and nextCursor. Each item has id (a tplv_ id), templateId, version, state, engine, subject, slots, props, publishedAt and createdAt.

مثال

var page = await client.Templates.ListVersionsAsync("order-shipped", limit: 10); foreach (var version in page){    Console.WriteLine($"v{version["version"]} {version["state"]}: {version["subject"]}");}

ملاحظات

  • An empty first page is impossible for an existing template, since version 1 is created with it. No published row anywhere in the walk means the template cannot be sent yet.

  • GetAsync returns only the head body. GetVersionAsync returns any one version's body, and passing the same number as version to PreviewAsync renders it.

  • A cursor this list did not hand out is a 400 invalid_cursor, and a template id or slug that names nothing is a 404.

متاح أيضًا في

API
GET /templates/{id}/versions
TypeScript
templates.listVersions()
Python
templates.list_versions()
Ruby
templates.list_versions
PHP
templates->listVersions
Go
Templates.ListVersions
Java
templates().listVersions
CLI
openemail templates list-versions

Templates.ListAllVersionsAsync

Collect every version of a template into one object

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
Task<IReadOnlyList<JsonObject>> ListAllVersionsAsync(    string idOrSlug,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Walks every page of a template's versions and returns all of them, newest first. It follows nextCursor until hasMore is false, one request per page.

A version published during the walk is newer than its first page and is not included. A version deleted during the walk is simply missing from the result.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

limitint?

Page size for each request, 1 to 100. The server defaults to 25.

cursorstring?

Starts the walk from this cursor instead of the newest version.

apiKeystring?

Overrides the client API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A list of JsonObject items holding every version of the template, newest first.

مثال

var versions = await client.Templates.ListAllVersionsAsync("order-shipped", limit: 100); Console.WriteLine(versions.Count);

ملاحظات

  • If any page fails the call throws and the versions already fetched are discarded.

  • Use IterateVersionsAsync to stop early, for example at the first published version.

متاح أيضًا في

API
GET /templates/{id}/versions
TypeScript
templates.listAllVersions()
Python
templates.list_all_versions()
Ruby
templates.list_all_versions
PHP
templates->listAllVersions
Go
Templates.ListAllVersions
Java
templates().listAllVersions

Templates.IterateVersionsAsync

Stream a template's versions one at a time, newest first

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
IAsyncEnumerable<JsonObject> IterateVersionsAsync(    string idOrSlug,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns an IAsyncEnumerable<JsonObject> over a template's versions that yields them individually and fetches the next page only when the current one is drained. Nothing is requested until you consume it, and breaking out of the loop stops further requests.

The walk ends when hasMore is false, when a page carries no nextCursor, or when the server repeats a cursor.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

limitint?

Page size per request, 1 to 100. The server defaults to 25.

cursorstring?

Starts the walk from this cursor instead of the newest version.

apiKeystring?

Overrides the client API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

An IAsyncEnumerable<JsonObject> that yields one version per step.

مثال

await foreach (var version in client.Templates.IterateVersionsAsync("order-shipped")){    if ((string?)version["state"] == "published")    {        Console.WriteLine($"Live is version {version["version"]}");         break;    }}

ملاحظات

  • The generator is lazy, so an abandoned loop costs only the pages it consumed.

متاح أيضًا في

API
GET /templates/{id}/versions
TypeScript
templates.iterateVersions()
Python
templates.iterate_versions()
Ruby
templates.iterate_versions
PHP
templates->iterateVersions
Go
Templates.IterateVersions
Java
templates().iterateVersions

Templates.GetVersionAsync

Read one version of a template, body included

الصلاحياتtemplates:read
التوقيع
Task<JsonObject> GetVersionAsync(    string idOrSlug,    int version,    string? apiKey = null,    CancellationToken cancellationToken = default)

One frozen revision with its body. document carries the block tree for the blocks engine and html carries the submitted markup for the html engine, alongside the subject, slots and props that were declared at the time.

This is what ListVersionsAsync leaves out. It is the only way to read an old version without changing anything: RestoreVersionAsync also shows you the body, but it moves the head to get there, so reading version 3 used to cost you your draft.

Reach for it to diff a regression against the revision that worked, to lift a block out of a design you have since replaced, or to record what a campaign actually said.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

versionintمطلوب

The version number to read, as ListVersionsAsync reports it. Not a tplv_ id.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with document and html populated, plus id, templateId, version, state, engine, subject, slots, props, publishedAt and createdAt. Only one of document and html holds anything; the other is null, and which it is follows engine.

مثال

var old = await client.Templates.GetVersionAsync("order-shipped", 3);var head = await client.Templates.GetAsync("order-shipped"); Console.WriteLine(old.ToJsonString()); Console.WriteLine(head.ToJsonString());

ملاحظات

  • A number nobody published, or one that was deleted, is a 404 template_version_not_found. Numbers are never reused, so a deleted one stays gone.

  • html is the markup exactly as it was submitted, not what went out: sanitising happens at publish, against the compiled copy.

  • Nothing is rendered here. Pass the same number as version to PreviewAsync to see it with values substituted.

  • Reading a version does not touch the draft. RestoreVersionAsync is still the call that brings an old body back.

متاح أيضًا في

API
GET /templates/{id}/versions/{version}
TypeScript
templates.getVersion()
Python
templates.get_version()
Ruby
templates.get_version
PHP
templates->getVersion
Go
Templates.GetVersion
Java
templates().getVersion
CLI
openemail templates get-version

Templates.PublishAsync

Publish the draft so sends use it

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> PublishAsync(    string idOrSlug,    int? expectedVersion = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Freezes the head version and makes it the one unpinned sends use. Compiling happens here: a blocks tree is rendered through react-email and html markup is sanitised, so a body that does not render fails with 422 invalid_template for the person publishing rather than for a recipient. Nothing is published when that happens.

The call is idempotent. If the head is already the published version it comes back unchanged, so a deploy script can publish on every run. Publishing also sets the template's status to active, which reactivates an archived template.

Only the head can be published, and there is no call to republish an older version. To keep production on an earlier version while a new one is prepared, pin that version on SendAsync. The response is the published version with the updated parent attached as template.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

expectedVersionint?

The version the draft is at as you read it. When somebody has saved the draft since, the call is a 409 version_conflict and nothing is published. Left out, whatever the draft holds now is published.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject for the head version with state set to published and publishedAt set, without document or html, plus template, an object for the parent template showing the new publishedVersion and status.

مثال

var published = await client.Templates.PublishAsync("order-shipped"); Console.WriteLine($"v{published["version"]} published at {published["publishedAt"]}");Console.WriteLine($"Live version {published["template"]?["publishedVersion"]}, {published["template"]?["status"]}");

ملاحظات

  • The SDK retries this call on network errors and retryable statuses, which is safe because publishing twice lands in the same place.

  • The server answers 201 even when nothing changed.

  • Sends that pin an earlier version are unaffected. Only unpinned sends move to the new version.

  • An html placeholder used inside an attribute with kind text fails here with param set to props.<key>.

متاح أيضًا في

API
POST /templates/{id}/versions
TypeScript
templates.publish()
Python
templates.publish()
Ruby
templates.publish
PHP
templates->publish
Go
Templates.Publish
Java
templates().publish
CLI
openemail templates publish

Templates.RestoreVersionAsync

Bring an older version's body back as the draft

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> RestoreVersionAsync(    string idOrSlug,    int version,    IReadOnlyDictionary<string, object?>? body = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Copies an older version's subject, body, slots and props forward into the head, which is how you undo a design you regret. Nothing is rolled back in place: the old version stays where it is in the list and the restored copy becomes the current draft.

Where it lands follows the usual rule. A published head mints version N+1 as a draft; a draft head is overwritten, so restoring twice does not pile up versions. Live sends do not move until you publish, so a restore is reversible until then: restore something else, or publish to commit.

Restoring the head itself is a 422, because there is nothing to bring back. An unknown version is a 404. expectedVersion makes the call safe against a concurrent editor, the same as on UpdateAsync.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

versionintمطلوب

The version whose body you want back, as ListVersionsAsync reports it.

expectedVersionint

The head version you read before restoring. A mismatch is a 409 version_conflict and nothing is written.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject shaped like the result of GetAsync, whose latest is the version this call wrote, plus restoredFrom, the version number it was copied from.

مثال

var restored = await client.Templates.RestoreVersionAsync("order-shipped", 2); Console.WriteLine($"Copied v{restored["restoredFrom"]} into draft v{restored["latest"]?["version"]}"); await client.Templates.PublishAsync("order-shipped");

ملاحظات

  • It does not publish. Until you call PublishAsync, sends keep resolving whatever was live before.

  • The restored body brings the old version's slots and props with it, so a send passing newer props may start failing with unknown_template_prop.

  • Restoring the current head is a 422 invalid_template, not a no-op.

  • Not retried by the SDK, because a repeat can mint a second version.

متاح أيضًا في

API
POST /templates/{id}/versions/{version}/restore
TypeScript
templates.restoreVersion()
Python
templates.restore_version()
Ruby
templates.restore_version
PHP
templates->restoreVersion
Go
Templates.RestoreVersion
Java
templates().restoreVersion
CLI
openemail templates restore-version

Templates.DeleteVersionAsync

Delete one version of a template

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> DeleteVersionAsync(    string idOrSlug,    int version,    string? apiKey = null,    CancellationToken cancellationToken = default)

Removes a single revision and leaves the template itself alone. It is for tidying a long version list, not for changing what sends.

Three versions cannot be deleted, and each refusal is a 422 rather than a silent success: the LIVE version, because sends resolve it; the HEAD, because that is the one being edited, and restoring an older version first is how you move off it; and the only version a template has, because a template with no versions could not be read at all, so delete the template instead.

Everything else is fair game. Mail already sent from a deleted version is untouched, since a send stores the body it rendered, but a send that pins a deleted version starts failing with template_version_not_found.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

versionintمطلوب

The version number to delete, as ListVersionsAsync reports it.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with object set to template_version, templateId, version and deleted set to true.

مثال

var deleted = await client.Templates.DeleteVersionAsync("order-shipped", 2); Console.WriteLine($"v{deleted["version"]} of {deleted["templateId"]} deleted");

ملاحظات

  • The live version is a 422 template_version_not_deletable. Publish another version first, then delete it.

  • The head is refused for the same code. Call RestoreVersionAsync with an older version, which makes a new head, and the old head becomes deletable.

  • Version numbers are never reused: deleting version 3 does not free the number.

  • Not retried by the SDK. Repeating a delete that succeeded is a 404.

متاح أيضًا في

API
DELETE /templates/{id}/versions/{version}
TypeScript
templates.deleteVersion()
Python
templates.delete_version()
Ruby
templates.delete_version
PHP
templates->deleteVersion
Go
Templates.DeleteVersion
Java
templates().deleteVersion
CLI
openemail templates delete-version

Templates.ListStartersAsync

List the built-in starter designs

الصلاحياتtemplates:read
التوقيع
Task<IReadOnlyList<JsonObject>> ListStartersAsync(    string? apiKey = null,    CancellationToken cancellationToken = default)

The starter designs the web editor offers, as a plain list. A starter is a ready-made block document with a subject and its declared slots and props, and it is the same catalogue the console shows, so an integration and a person building a template by hand start from the same place.

Bodies are left out here. slug is the handle to pass as starter to CreateAsync or ReplaceContentAsync, and GetStarterAsync returns one in full with its block tree and a rendered preview.

Starters are static: they are part of the product rather than workspace data, so this answer is the same for every key and changes only when a release adds one.

المعلمات

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A list of JsonObject items, each with slug, name, description, category (account, commerce, notify or marketing), subject, and the slots and props it declares.

مثال

foreach (var starter in (await client.Templates.ListStartersAsync())){    Console.WriteLine($"{starter["category"]} {starter["slug"]}: {string.Join(", ", (starter["props"]?.AsArray() ?? []).Select(row => row?["key"]))}");}

ملاحظات

  • Not paginated, and there is no cursor. The catalogue is small.

  • A starter's props are what the template gets on creation. They are yours to change afterwards with UpdateAsync.

متاح أيضًا في

API
GET /templates/starters
TypeScript
templates.listStarters()
Python
templates.list_starters()
Ruby
templates.list_starters
PHP
templates->listStarters
Go
Templates.ListStarters
Java
templates().listStarters
CLI
openemail templates list-starters

Templates.GetStarterAsync

Retrieve one starter design, body and preview included

الصلاحياتtemplates:read
التوقيع
Task<JsonObject> GetStarterAsync(    string slug,    string? apiKey = null,    CancellationToken cancellationToken = default)

One starter in full: everything the list carries, plus document, the block tree itself, and preview, the starter rendered to HTML with each undefaulted prop left visible as its key between double braces.

The preview is what the console shows in its starter picker, so a client can display the same thing without rendering anything itself. The document is there so you can seed a template from a starter and edit the tree before creating it, rather than creating from the starter and patching afterwards.

An unknown slug is a 404. Pass the slug exactly as ListStartersAsync reports it.

المعلمات

slugstringمطلوب

A starter slug from ListStartersAsync, such as welcome.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the starter's metadata plus document (the block tree, ready to send as document on CreateAsync) and preview (rendered HTML).

مثال

var starter = await client.Templates.GetStarterAsync("welcome"); var created = await client.Templates.CreateAsync(new Body{    ["name"] = "Welcome",    ["subject"] = starter["subject"],    ["document"] = starter["document"],    ["publish"] = true,}); Console.WriteLine($"{created["slug"]} v{created["publishedVersion"]}");

ملاحظات

  • Passing ["starter"] = "welcome" to CreateAsync does the same seeding server side, and is one call instead of two.

  • The preview is rendered once per process and cached, so it costs nothing to ask for it repeatedly.

متاح أيضًا في

API
GET /templates/starters/{slug}
TypeScript
templates.getStarter()
Python
templates.get_starter()
Ruby
templates.get_starter
PHP
templates->getStarter
Go
Templates.GetStarter
Java
templates().getStarter
CLI
openemail templates get-starter

Templates.ListFontsAsync

List the web fonts a template can load

الصلاحياتtemplates:read
التوقيع
Task<IReadOnlyList<JsonObject>> ListFontsAsync(    string? apiKey = null,    CancellationToken cancellationToken = default)

Every web font a template may load, as a plain list, in the order the web editor offers them. Each row names a family, the full CSS stack to write, the fallback a mail client shows when it cannot load the font, the weight range the file covers, and the url the file is served from.

The list is closed on purpose. A font file is fetched by the reader's mail client the moment the message is opened, so a font loaded from anywhere else would tell whoever runs that host when the message was read, whatever the workspace has open tracking set to. A template whose webFont.url is not the one listed here for its family is refused with 422 invalid_template.

Fonts are static: they are part of the product rather than workspace data, so this answer is the same for every key and changes only when a release adds one.

المعلمات

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A list of JsonObject items, each with object set to template_font, family, fallback, stack, weight, format and url, with format always woff2.

مثال

foreach (var font in (await client.Templates.ListFontsAsync())){    if ((string?)font["family"] == "Inter")    {        Console.WriteLine($"{font["stack"]} from {font["url"]}");    }}

ملاحظات

  • Not paginated, and there is no cursor. The catalogue is small.

  • One template may load at most 8 web fonts. A family that is not listed still renders: leave webFont out and it falls back to fallbackFontFamily, which is what Gmail and Outlook on Windows do with every web font anyway.

متاح أيضًا في

API
GET /templates/fonts
TypeScript
templates.listFonts()
Python
templates.list_fonts()
Ruby
templates.list_fonts
PHP
templates->listFonts
Go
Templates.ListFonts
Java
templates().listFonts
CLI
openemail templates list-fonts

Templates.RenderAsync

Render a body that is not stored anywhere

الصلاحياتtemplates:read
التوقيع
Task<JsonObject> RenderAsync(    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

Compiles and renders content you pass in, without creating a template or touching one. This is what the web editor calls while somebody types, and it is the call for checking a design in CI before it becomes a template.

Everything CreateAsync accepts as content is accepted here: document for the blocks engine, html for the html engine, plus subject, slots and props. values.props and values.slots fill the placeholders; anything left unfilled is rendered blank and reported in warnings, exactly as PreviewAsync does for a stored template.

["mark"] = true leaves every placeholder visible, its key still between double braces, instead of substituting it, which is how an editor shows an author what is a variable.

المعلمات

enginedictionary

Defaults to html when html is sent and blocks otherwise.

subjectstring

The subject to render, at most 998 characters.

documentdictionary

The blocks body for the blocks engine, validated in full: body plus preview, tailwind, fonts and style.

htmlstring

The markup for the html engine, at most 1,000,000 characters.

slotslist

The slots this body declares.

propslist

The props this body declares.

values.propsdictionary

Values for the declared props. A missing one renders blank and is reported.

values.slotsdictionary

Values for the declared slots, overriding their defaults.

markbool

Leaves each placeholder as its key between double braces rather than substituting it.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with subject, html, text and warnings, a list of objects each with code set to unfilled_placeholder and the key. Nothing is stored and nothing is sent.

مثال

var rendered = await client.Templates.RenderAsync(new Body{    ["subject"] = "Order {{orderId}} is on its way",    ["html"] = "<p>Hello {{customer}}, {{orderId}} left the warehouse.</p>",    ["props"] = new[]    {        new Body { ["key"] = "orderId", ["kind"] = "text", ["required"] = true },        new Body { ["key"] = "customer", ["kind"] = "text" },    },    ["values"] = new Body { ["props"] = new Body { ["orderId"] = "A-3311", ["customer"] = "Ada" } },}); Console.WriteLine($"{rendered["subject"]}"); foreach (var warning in rendered["warnings"]?.AsArray() ?? []){    Console.WriteLine($"Unfilled: {warning?["key"]}");}

ملاحظات

  • Needs only templates:read, because nothing is written. A body that does not compile is a 422 invalid_template naming the offending path.

  • Lenient like preview: a required prop with no value is a warning here and a refusal on a send.

  • Retried by the SDK on network errors, since rendering has no side effects.

متاح أيضًا في

API
POST /templates/render
TypeScript
templates.render()
Python
templates.render()
Ruby
templates.render
PHP
templates->render
Go
Templates.Render
Java
templates().render
CLI
openemail templates render

Templates.PreviewAsync

Render a template without sending it

الصلاحياتtemplates:read
التوقيع
Task<JsonObject> PreviewAsync(    string idOrSlug,    IReadOnlyDictionary<string, object?>? body = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Renders a version with the values you pass and returns the subject, HTML and plain text a send with the same values would produce. Nothing is sent or recorded. Point CI at it so a broken template is caught by a test rather than by a customer.

It renders the published version unless version names another one, and unlike SendAsync it can render a draft, which is compiled on the fly. A template with nothing published needs an explicit version, otherwise the call is a 422 template_not_published. Required props are relaxed here: a missing one renders its default or an empty string, and every placeholder that ends up blank is listed in warnings as unfilled_placeholder.

The other value checks still apply. A key the version does not declare is a 422 unknown_template_prop or unknown_template_slot, and a value that is not a string, number or boolean is a 422 invalid_template_prop. Values of kind url or image are parsed, and anything outside http, https, mailto, tel and cid renders as #.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

versionint

Version to render, drafts included. Defaults to the published version.

propsdictionary

Values for declared props, keyed by prop key. Strings, numbers and booleans only.

slotsdictionary

Overrides for slot defaults, keyed by slot key.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with templateId, the resolved version, the filled subject, html and text, and warnings, a list of objects with code and key.

مثال

var preview = await client.Templates.PreviewAsync("order-shipped", body: new Body{    ["props"] = new Body    {        ["orderId"] = "AC-4192",        ["customer"] = "Ada",        ["trackingUrl"] = "https://track.example.com/AC-4192",    },});Console.WriteLine($"v{preview["version"]}: {preview["subject"]}");

ملاحظات

  • Needs only templates:read, so a CI key can preview without being able to edit or send.

  • Assert that warnings is empty. SendAsync refuses a missing required prop that PreviewAsync only reports.

  • A version that does not exist is a 404. Treat an unrecognised warning code as a warning too.

  • The SDK retries it on network errors and retryable statuses, since rendering has no side effects.

متاح أيضًا في

API
POST /templates/{id}/preview
TypeScript
templates.preview()
Python
templates.preview()
Ruby
templates.preview
PHP
templates->preview
Go
Templates.Preview
Java
templates().preview
CLI
openemail templates preview

Templates.GetAnalyticsAsync

How one template has performed

الصلاحياتtemplates:read
التوقيع
Task<JsonObject> GetAnalyticsAsync(    string idOrSlug,    int? days = null,    int? minutes = null,    string? grain = null,    int? offsetMinutes = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

The engagement report the console shows on a template: how many messages it rendered in the window, how many of those were tracked, how many were opened and clicked, and the same numbers broken down by day, by source and by version.

Rates are computed against what was TRACKED, not against everything sent, because a message sent with tracking off can never report an open and counting it would quietly lower every rate. trackedForOpens and trackedForClicks are the denominators, and they are in the response so you can recompute anything yourself.

lifetime ignores the window: it is every live send this template has ever made, the test sends counted separately, and the first and last time it sent. recent previews the twelve newest sends in the window with their own open and click counts, which is the fastest way to see whether a template that was just published is behaving. It is a preview, not the list: ListSendsAsync returns every send in the window, a page at a time.

Only live sends are in the windowed figures. A message sent with a test key counts in the testSends of lifetime and nowhere else.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

daysint?

How far back to look, 1 to 365. Defaults to 30. The window starts at the beginning of that day and ends now.

minutesint?

The window in minutes, which wins over days. For the last hour of a send in flight.

grainstring?

How wide one byDay bucket is: day, hour or minute. The bucket keys change shape with it.

offsetMinutesint?

The reader's UTC offset in minutes, -840 to 840, so days are bucketed in their own timezone.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with sends, matched, trackedForOpens, trackedForClicks, opened, clicked, openRate, clickRate, totalOpens, totalClicks, the bySource, byDay and byVersion breakdowns, recent (a preview of the twelve newest sends, with every one of them a page at a time through ListSendsAsync) and lifetime.

مثال

var analytics = await client.Templates.GetAnalyticsAsync("order-shipped", days: 7, grain: "day"); Console.WriteLine($"{analytics["sends"]} sends, {analytics["openRate"]}% opened, {analytics["clickRate"]}% clicked"); foreach (var version in analytics["byVersion"]?.AsArray() ?? []){    Console.WriteLine($"v{version?["version"]}: {version?["sends"]} sends, {version?["opened"]} opened");}

ملاحظات

  • openRate and clickRate are percentages to one decimal place, and are 0 when nothing was tracked rather than null.

  • matched is how many renders were paired with a tracked message. A gap between sends and matched is sends that carried no tracking at all, not lost data.

  • A template that has never sent answers 200 with zeroes, not a 404. The 404 is for a template that does not exist.

  • Retried by the SDK on network errors, since it only reads.

متاح أيضًا في

API
GET /templates/{id}/analytics
TypeScript
templates.getAnalytics()
Python
templates.get_analytics()
Ruby
templates.get_analytics
PHP
templates->getAnalytics
Go
Templates.GetAnalytics
Java
templates().getAnalytics
CLI
openemail templates get-analytics

Templates.ListSendsAsync

The individual messages a template sent

الصلاحياتtemplates:read
التوقيع
Task<TemplateSends> ListSendsAsync(    string idOrSlug,    int? page = null,    int? pageSize = null,    string? search = null,    string? source = null,    int? version = null,    bool? opened = null,    bool? clicked = null,    bool? tracked = null,    int? days = null,    int? minutes = null,    string? grain = null,    int? offsetMinutes = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

One row per message this template rendered, newest first, with the subject as it went out, who it went to, and whether it was opened or clicked. It is the list behind the numbers GetAnalyticsAsync reports, and the place to answer "did this person get it".

Paging here is by page number rather than by cursor, because the console shows a table with a total, and total is the count matching the filters rather than the size of the page. Pages are 25 rows by default and at most 100.

The filters narrow by window (days: or minutes:), by version:, by source:, and by engagement: opened:, clicked: and tracked: each take true or false. search: matches the subject and the recipient addresses. A row whose message carried no tracking reports matched as false and zero counts, which is not the same as nobody opening it.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

pageint?

Which page, from 1. Defaults to 1.

pageSizeint?

Rows per page, 1 to 100. Defaults to 25.

searchstring?

Matches the subject that went out and the recipient addresses.

sourcestring?

Only sends from one source, such as api or console.

versionint?

Only sends that rendered this version number.

openedbool?

True for sends with at least one counted open, false for none.

clickedbool?

True for sends with at least one counted click, false for none.

trackedbool?

True for sends that carried tracking at all, false for the ones that could never report.

daysint?

How far back to look, 1 to 365. Defaults to 30.

minutesint?

The window in minutes, which wins over days.

grainstring?

Only floors the start of the window, so this list can cover the same window as GetAnalyticsAsync.

offsetMinutesint?

The reader's UTC offset in minutes, -840 to 840.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A TemplateSends with items, total (matching rows, not page size), page and pageSize. Looping over it or counting it goes through items. Each row has id, version, source, subject, createdAt, recipients, matched, opens, clicks, openCount and clickCount.

مثال

var sends = await client.Templates.ListSendsAsync("order-shipped", days: 7, opened: false, tracked: true, pageSize: 50); Console.WriteLine($"{sends.Total} sends nobody opened, page {sends.Page}"); foreach (var row in sends){    Console.WriteLine($"{row["createdAt"]} {string.Join(", ", row["recipients"]?.AsArray() ?? [])}");}

ملاحظات

  • opens and clicks say whether the message ASKED to be tracked; openCount and clickCount say what happened.

  • Only live sends are listed. Test-key sends are counted in the testSends of lifetime on GetAnalyticsAsync and appear nowhere here.

  • Page numbers are not stable while mail is going out, since a new send pushes rows down. Narrow the window rather than paging deep.

متاح أيضًا في

API
GET /templates/{id}/sends
TypeScript
templates.listSends()
Python
templates.list_sends()
Ruby
templates.list_sends
PHP
templates->listSends
Go
Templates.ListSends
Java
templates().listSends
CLI
openemail templates list-sends

Templates.SendAsync

Send an email rendered from a template

الصلاحياتtemplates:writeemails:send
التوقيع
Task<JsonObject> SendAsync(    string idOrSlug,    IReadOnlyDictionary<string, object?> body,    string? idempotencyKey = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Resolves the published version, or the one version pins, fills its placeholders from props and slots, and queues the message. Values are checked strictly here. An undeclared key is a 422 unknown_template_prop or unknown_template_slot, a missing required prop is a 422 missing_template_prop, and a value that is not a string, number or boolean is a 422 invalid_template_prop, each with param set to template.props.<key>. A template with nothing published, or a pinned version that is still a draft, is a 422 template_not_published. No mail leaves when any of these fail.

Pin version in production code. Without it every send resolves whatever is published at that moment, which changes the morning somebody publishes a rewrite. subject replaces the version's subject for this message only and is used exactly as written, with no placeholder filling. scheduledAt takes a DateTimeOffset (sent as an instant in UTC), an ISO 8601 instant or an ISO 8601 duration such as PT30M, up to 365 days out, and cannot be combined with a non zero cancellableForSeconds.

The SDK attaches an Idempotency-Key generated once per call and reuses it on that call's retries, so a retry replays the original message instead of sending a second one. Pass idempotencyKey: to deduplicate across processes and restarts, and derive it from what caused the send, never from a clock. A replay returns replayed set to true and the original message, while reusing a key with a different body is a 422 idempotency_key_reuse.

المعلمات

idOrSlugstringمطلوب

A tpl_ id or the template's slug.

fromstring or dictionaryمطلوب

Sender as address, Name <address> or a dictionary with email and name. The key must be allowed to send as it, otherwise 403 from_address_forbidden.

tostring or dictionary or listمطلوب

1 to 50 recipients, each a string or a dictionary with email and name. The SDK wraps a single value in a list.

ccstring or dictionary or list

Up to 50 copied recipients, in the same forms as to.

bccstring or dictionary or list

Up to 50 blind copied recipients, in the same forms as to.

replyTostring or dictionary

Sets the Reply-To header.

versionint

Published version to send. Defaults to the currently published one.

propsdictionary

Values for the declared props: strings, numbers or booleans, keyed by prop key.

slotsdictionary

Overrides for slot defaults, keyed by slot key.

subjectstring

Replaces the version's subject for this message, sent verbatim, at most 998 characters.

scheduledAtDateTimeOffset or string

When to send: a DateTimeOffset (sent as an ISO 8601 instant in UTC), an ISO 8601 instant string or a duration like PT2H. Must be in the future and at most 365 days out.

cancellableForSecondsint

Holds the message 0 to 900 seconds so it can still be cancelled. Defaults to 0 and is refused alongside scheduledAt.

trackingdictionary

A dictionary of per message opens and clicks switches, true or false, for open and click tracking.

tagsdictionary

Your own labels for the send, keys up to 64 and values up to 256 characters.

translatedictionary

A dictionary that translates the rendered message into the language in its to before sending, with optional from, includeOriginal (default true) and subject (default true).

idempotencyKeystring?

Your own key in place of the generated one: 1 to 255 characters of letters, digits, _, ., : or -.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the fields of a sent email such as id, status, mode, from, subject, messageId, threadId, scheduledAt, cancellableUntil, tags and createdAt, plus replayed and template, which holds the id you called with and the version that went out.

مثال

var sent = await client.Templates.SendAsync("order-shipped", new Body{    ["from"] = new Body { ["email"] = "[email protected]", ["name"] = "Acme Dispatch" },    ["to"] = "[email protected]",    ["version"] = 5,    ["props"] = new Body    {        ["orderId"] = "AC-4192",        ["customer"] = "Ada",        ["trackingUrl"] = "https://track.example.com/AC-4192",    },}, idempotencyKey: "order-shipped:AC-4192"); Console.WriteLine($"{sent["id"]} {sent["status"]}, v{sent["template"]?["version"]}{((bool?)sent["replayed"] == true ? " (replayed)" : "")}");

ملاحظات

  • A from on a domain that cannot sign mail yet is refused with 409 domain_not_sendable, the same as Emails.SendAsync.

  • Needs both templates:write and emails:send. A key that may send its own bodies still cannot send a stored template without the first.

  • The replay check hashes the request together with the version it resolved to. An unpinned retry that lands after somebody publishes a new version is refused with idempotency_key_reuse rather than replayed, so pin version when you rely on replays.

  • The id in template echoes the argument you passed, so it is the slug when you sent by slug. Call GetAsync if you need the tpl_ id.

  • An archived template is a 422 template_archived. An unknown template is a 404 resource_not_found, while a pinned version that does not exist is a 422 template_version_not_found with param set to template.version.

متاح أيضًا في

API
POST /templates/{id}/send
TypeScript
templates.send()
Python
templates.send()
Ruby
templates.send
PHP
templates->send
Go
Templates.Send
Java
templates().send
CLI
openemail templates send

Templates.ListImagesAsync

List one page of template images

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
Task<Page> ListImagesAsync(    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns one page of the images uploaded for templates, newest first: the library the template editor offers when you add an image. ListAllImagesAsync collects every page and IterateImagesAsync walks them lazily.

Template images belong to the workspace: every template in it can use them, and they stay at their address after the template that first used them is deleted.

المعلمات

limitint?

Page size, from 1 to 120. The server defaults to 24.

cursorstring?

The nextCursor of the previous page. Leave it out for the first page.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A Page with items, hasMore and nextCursor. Each item has id, url and size.

مثال

var page = await client.Templates.ListImagesAsync(limit: 10); foreach (var image in page){    Console.WriteLine($"{image["url"]} {image["size"]} bytes");}

ملاحظات

  • Needs templates:read.

متاح أيضًا في

API
GET /templates/images
TypeScript
templates.listImages()
Python
templates.list_images()
Ruby
templates.list_images
PHP
templates->listImages
Go
Templates.ListImages
Java
templates().listImages
CLI
openemail templates list-images

Templates.ListAllImagesAsync

Collect every template image into one object

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
Task<IReadOnlyList<JsonObject>> ListAllImagesAsync(    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Walks every page of ListImagesAsync and returns every template image, newest first. One request per page.

المعلمات

limitint?

Page size for each request, from 1 to 120. The server defaults to 24.

cursorstring?

Starts the walk after this cursor instead of the first page.

apiKeystring?

Overrides the client's API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A list of JsonObject items holding every image.

مثال

var images = await client.Templates.ListAllImagesAsync(); Console.WriteLine($"{string.Join(Environment.NewLine, images.Select(row => row?["url"]))}");

ملاحظات

  • If any page fails the call throws and the images already fetched are discarded.

متاح أيضًا في

API
GET /templates/images
TypeScript
templates.listAllImages()
Python
templates.list_all_images()
Ruby
templates.list_all_images
PHP
templates->listAllImages
Go
Templates.ListAllImages
Java
templates().listAllImages

Templates.IterateImagesAsync

Stream template images one at a time

الصلاحياتtemplates:readيتصفح النتائج صفحةً صفحة
التوقيع
IAsyncEnumerable<JsonObject> IterateImagesAsync(    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns an IAsyncEnumerable<JsonObject> that yields one template image at a time, newest first, and requests the next page only once the current one is drained.

المعلمات

limitint?

Page size for each request, from 1 to 120. The server defaults to 24.

cursorstring?

Starts the walk after this cursor instead of the first page.

apiKeystring?

Overrides the client's API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

An IAsyncEnumerable<JsonObject> that yields one image per step.

مثال

await foreach (var image in client.Templates.IterateImagesAsync()){    Console.WriteLine($"{image["id"]} {image["url"]}");}

ملاحظات

  • The generator is lazy, so an abandoned loop costs only the pages you consumed.

متاح أيضًا في

API
GET /templates/images
TypeScript
templates.iterateImages()
Python
templates.iterate_images()
Ruby
templates.iterate_images
PHP
templates->iterateImages
Go
Templates.IterateImages
Java
templates().iterateImages

Templates.UploadImageAsync

Upload an image for templates

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> UploadImageAsync(    Stream data,    string? contentType = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Sends the image bytes as the request body and returns its public url, ready to put in a template. It is the upload of the template editor.

PNG, JPEG, WebP, GIF or SVG, up to 5 MB, fitted into 1200 by 1800 pixels and stored in a form every mail client shows. The type is read from contentType:, and without it the server refuses the bytes with 422 invalid_image.

Template images belong to the workspace: every template in it can use them, and they stay at their address after the template that first used them is deleted.

المعلمات

dataStreamمطلوب

The image: a readable Stream.

contentTypestring?

image/png, image/jpeg, image/webp, image/gif or image/svg+xml, as in OpenEmail.Constants.TemplateImageTypes. Required, because a Stream carries no type of its own.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with id, url, size, width and height.

مثال

using OpenEmail.Constants; await using var photo = File.OpenRead("photo.jpg"); var image = await client.Templates.UploadImageAsync(photo); Console.WriteLine($"{image["url"]} {image["width"]}x{image["height"]}"); await using var logoFile = File.OpenRead("logo.png"); var logo = await client.Templates.UploadImageAsync(logoFile, contentType: TemplateImageTypes.Png); Console.WriteLine($"{logo["url"]}");

ملاحظات

  • The SDK does not retry an upload, because a second one stores a second copy.

  • An image of another type, one over 5 MB, or one that cannot be read is refused with 422 invalid_image. A busy image service answers 503 image_busy and a failed save 502 image_not_stored.

متاح أيضًا في

API
POST /templates/images
TypeScript
templates.uploadImage()
Python
templates.upload_image()
Ruby
templates.upload_image
PHP
templates->uploadImage
Go
Templates.UploadImage
Java
templates().uploadImage
CLI
openemail templates upload-image

Templates.DesignAsync

Design a new template from a brief

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> DesignAsync(    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

A designer builds a new block template from a written brief, the way the assistant in the app does, and saves it, as a draft unless publish is true. It uses every block the editor has, with a palette, type and spacing, and the result opens in the visual editor fully editable.

Put everything the design must hold in brief: what it is for, the sections in order, the words, the colours and fonts, and which values change per recipient. starter builds on a starter design, and imageFileIds places up to ten uploaded or received images. It spends one AI action and can take up to a minute.

المعلمات

namestringمطلوب

A short name for the template. When the name is taken, a free one is chosen and returned.

briefstringمطلوب

Everything the design must hold and look like, up to 8,000 characters.

descriptionstring

One line about what it is for.

starterstring

The slug of a starter design to build on, from ListStartersAsync.

imageFileIdsIEnumerable<string>

Up to ten file ids of images to place in the design.

publishbool

Publish it at once so it can be sent. Defaults to false.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the template, plus design, an object with the subject, the notes on what was adjusted and the imageProblems.

مثال

var template = await client.Templates.DesignAsync(new Body{    ["name"] = "Spring launch",    ["brief"] = "A launch email for our spring collection: a hero image, three product cards and a button to the shop. Green and cream, friendly tone.",}); Console.WriteLine($"{template["id"]}: {template["design"]?["subject"]}");Console.WriteLine($"{string.Join(Environment.NewLine, template["design"]?["notes"]?.AsArray() ?? [])}");

ملاحظات

  • The SDK does not retry it, because a second call designs and saves a second template.

  • A design that cannot be made valid is a 422 invalid_template, a server with no model a 409 ai_not_configured, and a workspace out of AI actions a 429 ai_quota_exceeded.

  • An image id the key cannot reach is left out and named in the imageProblems of design.

متاح أيضًا في

API
POST /templates/design
TypeScript
templates.design()
Python
templates.design()
Ruby
templates.design
PHP
templates->design
Go
Templates.Design
Java
templates().design
CLI
openemail templates design

Templates.RedesignAsync

Change a template’s design from instructions

الصلاحياتtemplates:write
التوقيع
Task<JsonObject> RedesignAsync(    string idOrSlug,    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

A designer applies written instructions to a block template and leaves everything else alone: restyle it, change colours, fonts or spacing, rewrite or translate its copy, add, move or remove sections, swap an image. The change lands in the draft, so live sends keep the published version until you publish.

A raw HTML template cannot be redesigned and is refused with 422 invalid_template. It spends one AI action.

المعلمات

idOrSlugstringمطلوب

The template's id or slug.

instructionsstringمطلوب

What to change, with every detail: the words, colours and which section. Up to 8,000 characters.

imageFileIdsIEnumerable<string>

Up to ten file ids of images to use.

expectedVersionint

The version you based the change on. A newer one is refused with 409 version_conflict.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

يُرجع

A JsonObject with the template, plus design, an object with changes (blocks edited, added and removed), draftVersion, the subject, notes and imageProblems.

مثال

var template = await client.Templates.RedesignAsync("spring-launch", new Body{    ["instructions"] = "Make the button orange and translate the copy into Spanish.",}); var changes = template["design"]?["changes"]; Console.WriteLine($"Draft v{template["design"]?["draftVersion"]}: {changes?["edited"]} edited, {changes?["added"]} added, {changes?["removed"]} removed");

ملاحظات

  • The SDK does not retry it, because each call is a fresh design pass.

  • The same errors as DesignAsync, plus 404 for a template that does not exist.

متاح أيضًا في

API
POST /templates/{id}/redesign
TypeScript
templates.redesign()
Python
templates.redesign()
Ruby
templates.redesign
PHP
templates->redesign
Go
Templates.Redesign
Java
templates().redesign
CLI
openemail templates redesign