查找

Article
· Jul 29 3m read

Dynamic Templated Emails in InterSystems IRIS with templated_email


Sending emails is a common requirement in integration scenarios — whether for client reminders, automatic reports, or transaction confirmations. Static messages quickly become hard to maintain and personalize. This is where the templated_email module comes in, combining InterSystems IRIS Interoperability with the power of Jinja2 templates.

Why Jinja2 for Emails

Jinja2 is a popular templating engine from the Python ecosystem that enables fully dynamic content generation. It supports:

  • Variables — inject dynamic data from integration messages or external sources
  • Conditions (if/else) — change the content based on runtime data
  • Loops (for) — generate tables, lists of items, or repeatable sections
  • Filters and macros — format dates, numbers, and reuse template blocks

Example of a simple email body template:

 


Hello {{ user.name }}!

{% if orders %}
You have {{ orders|length }} new orders:
{% for o in orders %}
 - Order #{{ o.id }}: {{ o.amount }} USD
{% endfor %}
{% else %}
You have no new orders today.
{% endif %}
 

With this approach, your email content becomes dynamic, reusable, and easier to maintain.

Email Styling and Rendering Considerations

Different email clients (Gmail, Outlook, Apple Mail, and others) may render the same HTML in slightly different ways. To achieve a consistent appearance across platforms, it is recommended to:

  • Use inline CSS instead of relying on external stylesheets
  • Prefer table‑based layouts for the overall structure, as modern CSS features such as flexbox or grid may not be fully supported
  • Avoid script and complex CSS rules, as many email clients block or ignore them
  • Test emails in multiple clients and on mobile devices

Capabilities of the templated_email Module

The templated_email module is designed to simplify creating and sending dynamic, professional emails in InterSystems IRIS. Its key capabilities include:

  • render Jinja2 templates to generate dynamic email content
  • Flexible usage — can be used both from Interoperability productions and directly from ObjectScript code
  • Built‑in Business Operation — ready‑to‑use operation for production scenarios with SMTP integration
  • Automatic inline styling — converts CSS styles into inline attributes for better email client compatibility
  • Markdown support — allows creating clean, maintainable templates that are rendered to HTML before sending

These features make it easy to produce dynamic, well‑formatted emails that display consistently across clients.

Module templated_email

The module provides the following key components:

  • TemplatedEmail.BusinessOperation — a Business Operation to send templated emails via SMTP
  • TemplatedEmail.EmailRequest — an Interoperability Message for triggering email sends, with fields for templates, data, and recipients
  • TemplatedEmail.MailMessage — an extension of %Net.MailMessage that adds:
    • applyBodyTemplate() — renders and sets the email body using a Jinja2 + Markdown template
    • applySubjectTemplate() — renders and sets the email subject using a Jinja2 template

This allows you to replace %Net.MailMessage with TemplatedEmail.MailMessage in ObjectScript and gain dynamic templating:


set msg = ##class(TemplatedEmail.MailMessage).%New()
do msg.applySubjectTemplate(data,"New order from {{ customer.name }}")
do msg.applyBodyTemplate(data,,"order_template.tpl")

More details and usage examples can be found in the GitHub repository.

Templates Are Not Just for Emails

The template rendering methods can also be used for generating HTML reports, making it easy to reuse the same templating approach outside of email.

With templated_email, you can create dynamic, professional, and maintainable emails for InterSystems IRIS — while leveraging the full flexibility of Jinja2 templates.

Discussion (0)1
Log in or sign up to continue
Announcement
· Jul 29

インターシステムズ 第23回 開発者向けウェビナー「RAG+生成AIであそぼう!」のご案内

 

いつもお世話になっております。

連続3回シリーズでお届けしている開発者向けウェビナー、最終回のテーマは「RAG+生成AIであそぼう!」です。

以下の日時で開催いたしますので、是非ご参加ください。

日時:9月9日(火)13時半~14時10分(40分間)

参加費無料・事前登録制

ご登録はこちらから

【概要】

生成AIにサンプルコードを依頼した時「よく見るとおかしいかも?」という経験をされた方も多いのではないでしょうか。また生成AIとのやりとりで、最初の質問には納得のいく答えが得られず「もっと良い回答をもらうにはどうすればいいか」と考えて質問を言い換えたり、前提情報を追加したりした経験もあるはずです。これは、AIに渡す情報の「質」と「量」が、出力結果の精度に直結することを示しています。 このような課題に対して有効なのが、RAG(検索拡張生成:Retrieval-Augmented Generation)というアプローチで、信頼できる情報源を AI に組み合わせることで、より正確で実用的な回答を得ることができるようになります。 本ウェビナーでは、RAGとは何か、その仕組みと利点、どのような場面で効果を発揮するのかを、開発者目線でわかりやすく解説します。

さらにInterSystems IRIS を活用して RAG を実装する方法を具体的なコードを交えながらご紹介し、生成 AI に高品質な回答をさせるための実践的な方法を解説します。

デモでは、ミートアップで一緒に作成していただく予定の RAG + 生成AI の Web API 実行例をご覧いただきます。さらにWeb API を生成 AI とアプリの接続に便利な MCP サーバとして公開し、Claude desktop から利用する例をご覧いただく予定です。

【こんな方にお勧め】

RAG、生成AIに興味があり、開発をしてみたい方

ご多用中とは存じますが、皆様のご参加をお待ち申し上げております。

Discussion (0)1
Log in or sign up to continue
Question
· Jul 28

Adding a Angular configuration page to the IRIS Management portal

With Intersystems indicating ZEN pages are being deprecated, I'm looking to find out how to add custom configuration pages for the management portal using the new method similar to the new Rules editor.

From what I can tell the new method uses rest pages using JWT Authentication, and has a mechanism to use the session cookie to generate the JWT token.

I've gotten the REST part done as per:

Creating a REST API with JWT Authentication in ObjectScript | InterSystems
 

The pages for the rules editor are using an obfuscated/minimised angular pages and include the following packages that handle the look, feel and authentication, but can't find these provided anywhere for use outside of InterSystems.

I'm checking if these are available anywhere. Or at least if anyone has the authentication/token handling that I could use within a custom config page.

The primary part that I couldn't follow completely was getting the token using the session cookie so I don't need an extra login for the custom config pages.

@intersystems/about-dialog
@intersystems/auth
@intersystems/column-view
@intersystems/confirmation-dialog
@intersystems/header
@intersystems/isc-form
@intersystems/notification
@intersystems/shared-utilities
@intersystems/styles
@intersystems/table
@intersystems/timeout

Discussion (0)3
Log in or sign up to continue
Article
· Jul 28 8m read

Creación de documentos Word avanzados con IRIS

Los documentos de Word se utilizan ampliamente en el mercado. Los usuarios crean con frecuencia contratos, memorandos, currículums, informes, análisis y otros documentos que pueden requerir datos de InterSystems IRIS o que este los capture. Sin embargo, IRIS no dispone de una API, SDK, biblioteca ni adaptador para ello. Esta limitación ya no existe.

La nueva biblioteca de Open Exchange, iris4word (https://openexchange.intersystems.com/package/iris4word), ofrece un SDK de ObjectScript donde el desarrollador pasa cualquier %DynamicObject como parámetro, una plantilla de archivo de Word y recibe un documento listo para usar, con la estructura y el formato definidos en su plantilla.


Para utilizar iris4word

Este artículo utilizará una API REST para obtener el contenido, pero también es posible consultar la base de datos. Para instalar iris4word y el ejemplo que lo utiliza, siga estos pasos:

  1. Si utiliza IPM/ZPM: zpm:USER>install iris4word
  2. Si usas Docker: 
    1. git clone https://github.com/yurimarx/iris4word.git
    2. docker-compose up -d --build
  3. Abra el Postman (para ejecutar el ejemplo de API REST)
  4. Importar la colección de muestras desde (https://raw.githubusercontent.com/yurimarx/iris4word/refs/heads/master/iris4word.postman_collection.json): 
  5. Sube la plantilla de archivo template.docx (en la ruta sample/template.docx de este repositorio o desde https://raw.githubusercontent.com/yurimarx/iris4word/refs/heads/master/sample/template.docx). Para cargar, complete el campo de archivo en la pestaña Body: 
  6.  El proceso de carga enviará la plantilla al servidor para que la utilice iris4word.
  7. Abra el documento de Word 2. Descargue y copie y pegue en el cuerpo este contenido JSON: 
    {
      "company": {
        "name": "ACM Ltda.",
        "address": "Main Street, 123",
        "city": "New York",
        "state": "NY"
      },
      "projects": [
        {
          "name": "System Development X",
          "beginDate": "2024-01-01",
          "endDate": "2024-06-06",
          "team": [
            {"name": "John Star", "role": "Senior Developer"},
            {"name": "Marie Loo", "role": "BDM"}
          ],
          "tasks": [
            {"description": "Requirements", "status": "Done"},
            {"description": "Development", "status": "Doing"}
          ]
        },
        {
          "name": "ERP Development Y",
          "beginDate": "2024-03-03",
          "endDate": "2025-12-12",
          "team": [
            {"name": "Peter Rogers", "role": "Project Manager"},
            {"name": "Robert Plant", "role": "ERP Specialist"}
          ],
          "tasks": [
            {"description": "ERP configuration", "status": "Done"},
            {"description": "User training", "status": "Doing"}
          ]
        }
      ],
      "principalContact": {
        "name": "Carlos Olivera",
        "email": "carlos.olivera@company.com",
        "phone": "+1 555 555-555"
      }
    }
     
  8. En el botón Enviar, seleccione la opción Send and Download:
  9. Vea los resultados:
  10. Compare el contenido JSON con la plantilla.docx y vea las marcas y etiquetas utilizadas.

Entre bastidores

Es muy fácil, con el archivo de plantilla guardado en /tmp/template.docx, simplemente llámelo:

ClassMethod DownloadDoc(template As %String) As %Status
{
    Set tUUID = $System.Util.CreateGUID() 
    Set filePath = "/tmp/"_tUUID_".docx"
    Set jsonContentString = {}.%FromJSON(%request.Content)
    Set sc = ##class(dc.iris4word.WordUtil).GenerateWordFileFromJSON(jsonContentString.%ToJSON(), "/tmp/"_template_".docx", filePath)

    Set %response.NoCharSetConvert=1
    Set %response.Headers("Access-Control-Allow-Origin")="*"
    Do %response.SetHeader("Content-Type","application/vnd.openxmlformats-officedocument.wordprocessingml.document")
    Do %response.SetHeader("Content-Disposition","attachment;filename="""_tUUID_".docx"_"""")
    
    Set stream=##class(%Stream.FileBinary).%New()
    Set sc=stream.LinkToFile(filePath)
    Do stream.OutputToDevice()
    
    Return sc
}

El método de clase ##class(dc.iris4word.WordUtil).GenerateWordFileFromJSON recibe los datos en forma de %DynamicObject, la ruta de la plantilla de Word y la ruta del archivo donde se creará el documento final. Ahora tienes un archivo de Word para enviar al usuario como respuesta. ¡Muy fácil!

El lenguaje de plantilla

iris4word le ofrece un lenguaje de plantilla flexible para componer documentos de Word dinámicos utilizando el editor de Microsoft Word.

Text tag

La etiqueta de texto es el tipo de etiqueta más básico en la plantilla de Word. {{name}} se reemplazará por el valor de la clave name en el modelo de datos. Si la clave no existe, la etiqueta se borrará (el programa puede configurar si se conserva la etiqueta o se lanza una excepción).

Template:

{{name}} always said life was like a box of {{thing}}.

Producción:

Mama always said life was like a box of chocolates.

Picture tag

La etiqueta de imagen empieza con @. Por ejemplo, {{@logo}} buscará el valor con la clave del logo en el modelo de datos y reemplazará la etiqueta con la imagen. Los datos correspondientes a la etiqueta de imagen pueden ser una URL simple, una cadena de ruta o una estructura que contenga el ancho y el alto de la imagen.

Template:

Fruit Logo:
watermelon {{@watermelon}}
lemon {{@lemon}}
banana {{@banana}}

Producción:

Fruit Logo:
watermelon 🍉
lemon 🍋
banana 🍌

Numeración

La etiqueta de lista corresponde a la lista de símbolos o lista numerada de Word, que comienza con *, como {{*number}}.

Template:

{{*list}}


Producción:

● Plug-in grammar
● Supports word text, pictures, table...
● Templates, not just templates, but also style templates

Secciones

Una sección se compone de dos etiquetas, una antes y otra después. La etiqueta de inicio se identifica con ? y la etiqueta de fin, con /. Por ejemplo, {{?section}} es la etiqueta de inicio del bloque de secciones, {{/section}} es la etiqueta de fin y "section" es el nombre de esta sección.

Las secciones son muy útiles al procesar una serie de elementos de documento. Los elementos de documento (texto, imágenes, tablas, etc.) ubicados en una sección pueden renderizarse cero, una o N veces, según el valor de la sección.

Valores falsos o colección vacía

Si el valor de la sección es nulo, falso o una colección vacía, no se mostrarán todos los elementos del documento ubicados en la sección, de forma similar a la condición de la sentencia if, que es falsa.

Modelo de datos:

{
  "announce": false
}
Template:

Made it,Ma!{{?announce}}Top of the world!{{/announce}}
Made it,Ma!
{{?announce}}
Top of the world!🎋
{{/announce}}

Producción:

Made it,Ma!
Made it,Ma!

Valores no falsos y no una colección

Si el valor de la sección no es nulo, falso ni una colección, todos los elementos del documento de la sección se renderizarán una vez, de forma similar a la condición de que la instrucción if sea verdadera.

Modelo de datos:

{
  "person": { "name": "Sayi" }
}
Template:

{{?person}}
  Hi {{name}}!
{{/person}}

Producción:

  Hi Sayi!

Colección no vacía

Si el valor de la sección es una colección no vacía, los elementos del documento de la sección se repetirán una o N veces, según el tamaño de la colección, de forma similar a la sintaxis foreach.

Modelo de datos:

{
  "songs": [
    { "name": "Memories" },
    { "name": "Sugar" },
    { "name": "Last Dance" }
  ]
}
Template:

{{?songs}}
{{name}}
{{/songs}}

Producción:

Memories
Sugar
Last Dance
In the loop, a special tag {{=#this}} can be used to directly refer to the object of the current iteration.

Modelo de datos:

{
  "produces": [
    "application/json",
    "application/xml"
  ]
}
Template:

{{?produces}}
{{=#this}}
{{/produces}}

Producción:

application/json
application/xml

El código Java desarrollado para crear la biblioteca iris4word

iris4word se creó con Java. InterSystems IRIS admite el uso de bibliotecas Java con la función de puerta de enlace externa de Java:

ClassMethod GenerateWordFileFromJSON(jsonContent As %String, wordTemplateFileName As %String, wordFileName As %String) As %Status
{
    Set tSC = $$$OK
    Try {
        
        set javaGate = $system.external.getJavaGateway()  
        do javaGate.addToPath($SYSTEM.Util.BinaryDirectory()_"iris4word-1.0.0-jar-with-dependencies.jar")
        set wordUtil = javaGate.new("iris4word.WordUtil")
...
}

La instrucción $system.external.getJavaGateway devolvió un motor Java y addToPath nos permite usar el archivo iris4word-1.0.0.0-jar-with-dependencies.jar. Esta biblioteca tiene la implementación de iris4word:

package iris4word;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.Map;

public class WordUtil {

    public void createFromFile(String jsonFileName, String wordTemplateFileName, String wordFileName) {
        
    	java.util.logging.Logger julLogger2 = java.util.logging.Logger.getLogger("com.deepoove.poi.render.processor.LogProcessor");
    	julLogger2.setLevel(java.util.logging.Level.OFF);
    	
    	java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger("org.slf4j.Logger");
        julLogger.setLevel(java.util.logging.Level.OFF);
        
    	ObjectMapper objectMapper = new ObjectMapper(); 
    	XWPFTemplate template = null;
    	
        try {
            File jsonFile = new File(jsonFileName);
            
            @SuppressWarnings("unchecked")
			Map<String, Object> data = objectMapper.readValue(jsonFile, Map.class);

            Configure config = Configure.builder().build();

            template = XWPFTemplate.compile(wordTemplateFileName, config).render(data);

            template.writeToFile(wordFileName);
            

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	if(template != null) {        		
        		try {
					template.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
        	}
		}
    }
	
}

Iris4word utilizó la biblioteca de código abierto poi-tl (paquete com.deepoove.poi) para renderizar plantillas de Word con datos dinámicos. Iris4word transformó los datos JSON al formato poi-tl, los convirtió a poi-tl, ejecutó el proceso de renderizado y obtuvo el documento de Word. El documento se guardó en el disco para su uso en código ObjectScript.

ClassMethod GenerateWordFile(jsonFileName As %String, wordTemplateFileName As %String, wordFileName As %String) As %Status
{
    Set tSC = $$$OK
    Try {
        
        set javaGate = $system.external.getJavaGateway()  
        do javaGate.addToPath($SYSTEM.Util.BinaryDirectory()_"iris4word-1.0.0-jar-with-dependencies.jar")
        set wordUtil = javaGate.new("iris4word.WordUtil") 
        
        do wordUtil.createFromFile(jsonFileName, wordTemplateFileName, wordFileName)
        
    }
    Catch ex {
        Set tSC=ex.AsStatus()
    }

    return tSC
}

Este proyecto demuestra cómo no solo Python, sino también Java, pueden ampliar significativamente la funcionalidad de IRIS. ¡Disfrútalo!

Discussion (0)1
Log in or sign up to continue
Question
· Jul 28

Schema Design Best Practices for Cross-Departmental Data Lakes in IRIS

We’re building out a data lake in IRIS 2025.1 that aggregates data across multiple business systems and departments. I’m trying to establish best practices for schema design and separation.

Right now, I’m thinking of using a separate schema for each distinct system of record feeding into the data lake - for example, one schema per upstream source system, rather than splitting based on function (e.g. staging, raw, curated). The idea is that this would make it easier to manage source ownership, auditing, and pipeline logic, especially when multiple domains are contributing data.

But I’d love to hear what others have done. Specifically:

  • What’s the best way to structure schemas in a cross-departmental data lake?
  • Do you separate by source system, business function, or lifecycle stage?
  • Are there downsides to having many small schemas instead of one large shared schema?
  • How do you manage naming, discoverability, and access control across schemas?

Any examples, patterns, or anti-patterns would be helpful.

Thanks!

Discussion (0)2
Log in or sign up to continue