Repository: vocdoni/saas-backend
Pull request #112: Adding email support endpoint
Author: login: emmdim
Created: 2025-05-15T15:15:37Z | Merged: 2025-05-22T09:15:35Z

Relevant to https://github.com/vocdoni/saas-backend/issues/105


Diff:
diff --git a/api/api.go b/api/api.go
index 4ebc5966..9f504ad4 100644
--- a/api/api.go
+++ b/api/api.go
@@ -251,6 +251,9 @@ func (a *API) initRouter() http.Handler {
 		// delete organization meta information
 		log.Infow("new route", "method", "DELETE", "path", organizationMetaEndpoint)
 		r.Delete(organizationMetaEndpoint, a.deleteOrganizationMetaHandler)
+		// create a new ticket for the organization
+		log.Infow("new route", "method", "POST", "path", organizationCreateTicketEndpoint)
+		r.Post(organizationCreateTicketEndpoint, a.organizationCreateTicket)
 		// handle stripe checkout session
 		log.Infow("new route", "method", "POST", "path", subscriptionsCheckout)
 		r.Post(subscriptionsCheckout, a.createSubscriptionCheckoutHandler)
diff --git a/api/apicommon/const.go b/api/apicommon/const.go
index 89d25017..a65c7930 100644
--- a/api/apicommon/const.go
+++ b/api/apicommon/const.go
@@ -20,4 +20,6 @@ const (
 	// InvitationExpiration is the duration of the invitation code before it is
 	// invalidated
 	InvitationExpiration = 5 * 24 * time.Hour // 5 days
+	// Support Email is the email address used for support requests
+	SupportEmail = "support@vocdoni.org"
 )
diff --git a/api/apicommon/types.go b/api/apicommon/types.go
index 7446d60b..796e21bd 100644
--- a/api/apicommon/types.go
+++ b/api/apicommon/types.go
@@ -853,3 +853,14 @@ type OAuthServiceAddressResponse struct {
 	// The address of the OAuth service signer
 	Address string `json:"address"`
 }
+
+type CreateOrganizationTicketRequest struct {
+	// Type of the ticket to create (definded externally)
+	TicketType string `json:"type"`
+
+	// Title of the ticket
+	Title string `json:"title"`
+
+	// Body of the ticket
+	Description string `json:"description"`
+}
diff --git a/api/docs.md b/api/docs.md
index d91a4438..de72680d 100644
--- a/api/docs.md
+++ b/api/docs.md
@@ -37,6 +37,7 @@
   - [🔍 Check Add Participants Job Status](#-check-add-participants-job-status)
   - [❌ Delete Organization Participants](#-delete-organization-participants)
   - [📋 Organization Meta Information](#-organization-meta-information)
+  - [🎫 Create Organization Ticket](#-create-organization-ticket)
   - [🤠 Available organization members roles](#-available-organization-members-roles)
   - [🏛️ Available organization types](#-available-organization-types)
 - [🏦 Plans](#-plans)
@@ -1046,6 +1047,33 @@ Deletes multiple participants from an organization by their participant IDs. Req
 | `400` | `40011` | `no organization provided` |
 | `500` | `50002` | `internal server error` |
 
+### 🎫 Create Organization Ticket
+
+* **Path** `/organizations/{address}/ticket`
+* **Method** `POST`
+* **Headers**
+  * `Authentication: Bearer <user_token>`
+* **Request body**
+```json
+{
+  "type": "support",
+  "title": "Need help with voting process",
+  "description": "I'm having trouble setting up a new voting process. Can you help?"
+}
+```
+
+* **Description**
+Creates a new support ticket for the organization. The user must be a member of the organization with any role. The ticket is sent to the support team via email.
+
+* **Errors**
+
+| HTTP Status | Error code | Message |
+|:---:|:---:|:---|
+| `401` | `40001` | `user not authorized` |
+| `400` | `40004` | `malformed JSON body` |
+| `400` | `40011` | `no organization provided` |
+| `500` | `50002` | `internal server error` |
+
 ### 📋 Organization Meta Information
 
 * **Path** `/organizations/{address}/meta`
diff --git a/api/organization_meta.go b/api/organization_meta.go
index f0522a1e..fe713dc3 100644
--- a/api/organization_meta.go
+++ b/api/organization_meta.go
@@ -65,7 +65,7 @@ func (a *API) addOrganizationMetaHandler(w http.ResponseWriter, r *http.Request)
 //	@Description	Updates existing or adds new key/value pairs in the meta information of an organization.
 //	@Description	Has only one layer of depth. If a second layer document is provided, for example meta.doc = [a,b,c],
 //	@Description	the entire document will be updated.
-//	@Description 	Requires Manager/Admin role.
+//	@Description	Requires Manager/Admin role.
 //	@Tags			organizations
 //	@Accept			json
 //	@Produce		json
diff --git a/api/organizations.go b/api/organizations.go
index d096c8cb..173beda3 100644
--- a/api/organizations.go
+++ b/api/organizations.go
@@ -711,3 +711,79 @@ func (a *API) organizationCensusesHandler(w http.ResponseWriter, r *http.Request
 	}
 	apicommon.HTTPWriteJSON(w, result)
 }
+
+// organizationCreateTicket godoc
+//
+//	@Summary		Create a new ticket for an organization
+//	@Description	Create a new ticket for an organization. The user must be a member of the organization with any role.
+//	@Tags			organizations
+//	@Accept			json
+//	@Produce		json
+//	@Security		BearerAuth
+//	@Param			address	path		string										true	"Organization address"
+//	@Param			request	body		apicommon.CreateOrganizationTicketRequest	true	"Ticket request information"
+//	@Success		200		{string}	string										"OK"
+//	@Failure		400		{object}	errors.Error								"Invalid input data"
+//	@Failure		401		{object}	errors.Error								"Unauthorized"
+//	@Failure		404		{object}	errors.Error								"Organization not found"
+//	@Failure		500		{object}	errors.Error								"Internal server error"
+//	@Router			/organizations/{address}/ticket [post]
+func (a *API) organizationCreateTicket(w http.ResponseWriter, r *http.Request) {
+	// get the user from the request context
+	user, ok := apicommon.UserFromContext(r.Context())
+	if !ok {
+		errors.ErrUnauthorized.Write(w)
+		return
+	}
+	// get the organization info from the request context
+	org, _, ok := a.organizationFromRequest(r)
+	if !ok {
+		errors.ErrNoOrganizationProvided.Write(w)
+		return
+	}
+	if !user.HasRoleFor(org.Address, db.AnyRole) {
+		errors.ErrUnauthorized.Withf("user is not a member of organization").Write(w)
+		return
+	}
+	// get the ticket request from the request body
+	ticketReq := &apicommon.CreateOrganizationTicketRequest{}
+	if err := json.NewDecoder(r.Body).Decode(ticketReq); err != nil {
+		errors.ErrMalformedBody.Write(w)
+		return
+	}
+	// validate the ticket request
+	if ticketReq.Title == "" || ticketReq.Description == "" {
+		errors.ErrMalformedBody.With("title and description are required").Write(w)
+		return
+	}
+
+	if !internal.ValidEmail(user.Email) {
+		errors.ErrEmailMalformed.With("invalid user email address").Write(w)
+		return
+	}
+	notification, err := mailtemplates.SupportNotification.ExecTemplate(
+		struct {
+			Type         string
+			Organization string
+			Title        string
+			Description  string
+			Email        string
+		}{ticketReq.TicketType, org.Address, ticketReq.Title, ticketReq.Description, user.Email},
+	)
+	if err != nil {
+		log.Warnw("could not execute support notification template", "error", err)
+		errors.ErrGenericInternalServerError.Write(w)
+		return
+	}
+
+	notification.ToAddress = apicommon.SupportEmail
+	notification.ReplyTo = user.Email
+
+	// send an email to the support destination
+	if err := a.mail.SendNotification(r.Context(), notification); err != nil {
+		log.Warnw("could not send ticket notification email", "error", err)
+		errors.ErrGenericInternalServerError.Write(w)
+		return
+	}
+	apicommon.HTTPWriteOK(w)
+}
diff --git a/api/routes.go b/api/routes.go
index 3895cbad..0bdfe983 100644
--- a/api/routes.go
+++ b/api/routes.go
@@ -72,6 +72,8 @@ const (
 	organizationDeleteParticipantsEndpoint = "/organizations/{address}/participants"
 	// POST/PUT/GET/DELETE /organizations/{address}/meta to add/set/get/delete the organization metadata
 	organizationMetaEndpoint = "/organizations/{address}/meta"
+	// POST /organizations/{address}/ticket to create a new ticket to our support system
+	organizationCreateTicketEndpoint = "/organizations/{address}/ticket"
 
 	// subscription routes
 	// GET /subscriptions to get the subscriptions of an organization
diff --git a/assets/support.html b/assets/support.html
new file mode 100644
index 00000000..bb1c726a
--- /dev/null
+++ b/assets/support.html
@@ -0,0 +1,563 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
+    xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
+
+<head>
+    <title></title>
+    <meta charset="UTF-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <!--[if !mso]>-->
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <!--<![endif]-->
+    <meta name="x-apple-disable-message-reformatting" content="" />
+    <meta content="target-densitydpi=device-dpi" name="viewport" />
+    <meta content="true" name="HandheldFriendly" />
+    <meta content="width=device-width" name="viewport" />
+    <meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no" />
+    <style type="text/css">
+        table {
+            border-collapse: separate;
+            table-layout: fixed;
+            mso-table-lspace: 0pt;
+            mso-table-rspace: 0pt
+        }
+
+        table td {
+            border-collapse: collapse
+        }
+
+        .ExternalClass {
+            width: 100%
+        }
+
+        .ExternalClass,
+        .ExternalClass p,
+        .ExternalClass span,
+        .ExternalClass font,
+        .ExternalClass td,
+        .ExternalClass div {
+            line-height: 100%
+        }
+
+        body,
+        a,
+        li,
+        p,
+        h1,
+        h2,
+        h3 {
+            -ms-text-size-adjust: 100%;
+            -webkit-text-size-adjust: 100%;
+        }
+
+        html {
+            -webkit-text-size-adjust: none !important
+        }
+
+        body,
+        #innerTable {
+            -webkit-font-smoothing: antialiased;
+            -moz-osx-font-smoothing: grayscale
+        }
+
+        #innerTable img+div {
+            display: none;
+            display: none !important
+        }
+
+        img {
+            Margin: 0;
+            padding: 0;
+            -ms-interpolation-mode: bicubic
+        }
+
+        h1,
+        h2,
+        h3,
+        p,
+        a {
+            line-height: inherit;
+            overflow-wrap: normal;
+            white-space: normal;
+            word-break: break-word
+        }
+
+        a {
+            text-decoration: none
+        }
+
+        h1,
+        h2,
+        h3,
+        p {
+            min-width: 100% !important;
+            width: 100% !important;
+            max-width: 100% !important;
+            display: inline-block !important;
+            border: 0;
+            padding: 0;
+            margin: 0
+        }
+
+        a[x-apple-data-detectors] {
+            color: inherit !important;
+            text-decoration: none !important;
+            font-size: inherit !important;
+            font-family: inherit !important;
+            font-weight: inherit !important;
+            line-height: inherit !important
+        }
+
+        u+#body a {
+            color: inherit;
+            text-decoration: none;
+            font-size: inherit;
+            font-family: inherit;
+            font-weight: inherit;
+            line-height: inherit;
+        }
+
+        a[href^="mailto"],
+        a[href^="tel"],
+        a[href^="sms"] {
+            color: inherit;
+            text-decoration: none
+        }
+    </style>
+    <style type="text/css">
+        @media (min-width: 481px) {
+            .hd {
+                display: none !important
+            }
+        }
+    </style>
+    <style type="text/css">
+        @media (max-width: 480px) {
+            .hm {
+                display: none !important
+            }
+        }
+    </style>
+    <style type="text/css">
+        @media (max-width: 480px) {
+
+            .t26,
+            .t52 {
+                text-align: left !important
+            }
+
+            .t25,
+            .t51 {
+                vertical-align: top !important;
+                width: 600px !important
+            }
+        }
+    </style>
+    <!--[if !mso]>-->
+    <link
+        href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;500;700&amp;family=Roboto:wght@400;700&amp;display=swap"
+        rel="stylesheet" type="text/css" />
+    <!--<![endif]-->
+    <!--[if mso]>
+<xml>
+<o:OfficeDocumentSettings>
+<o:AllowPNG/>
+<o:PixelsPerInch>96</o:PixelsPerInch>
+</o:OfficeDocumentSettings>
+</xml>
+<![endif]-->
+</head>
+
+<body id=body class=t62 style="min-width:100%;Margin:0px;padding:0px;background-color:#F0F0F0;">
+    <div class=t61 style="background-color:#F0F0F0;">
+        <table role=presentation width=100% cellpadding=0 cellspacing=0 border=0 align=center>
+            <tr>
+                <td class=t60 style="font-size:0;line-height:0;mso-line-height-rule:exactly;background-color:#F0F0F0;"
+                    valign=top align=center>
+                    <!--[if mso]>
+<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
+<v:fill color=#F0F0F0/>
+</v:background>
+<![endif]-->
+                    <table role=presentation width=100% cellpadding=0 cellspacing=0 border=0 align=center id=innerTable>
+                        <tr>
+                            <td>
+                                <div class=t30
+                                    style="mso-line-height-rule:exactly;mso-line-height-alt:70px;line-height:70px;font-size:1px;display:block;">
+                                    &nbsp;&nbsp;</div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align=center>
+                                <table class=t34 role=presentation cellpadding=0 cellspacing=0
+                                    style="Margin-left:auto;Margin-right:auto;">
+                                    <tr>
+                                        <td width=600 class=t33 style="width:600px;">
+                                            <table class=t32 role=presentation cellpadding=0 cellspacing=0 width=100%
+                                                style="width:100%;">
+                                                <tr>
+                                                    <td class=t31
+                                                        style="overflow:hidden;background-color:#FFFFFF;padding:40px 40px 66px 40px;border-radius:20px 20px 20px 20px;">
+                                                        <div class=t29 style="width:100%;text-align:left;">
+                                                            <div class=t28 style="display:inline-block;">
+                                                                <table class=t27 role=presentation cellpadding=0
+                                                                    cellspacing=0 align=left valign=top>
+                                                                    <tr class=t26>
+                                                                        <td></td>
+                                                                        <td class=t25 width=520 valign=top>
+                                                                            <table role=presentation width=100%
+                                                                                cellpadding=0 cellspacing=0 class=t24
+                                                                                style="width:100%;">
+                                                                                <tr>
+                                                                                    <td class=t23
+                                                                                        style="overflow:hidden;background-color:transparent;border-radius:20px 20px 20px 20px;">
+                                                                                        <table role=presentation
+                                                                                            width=100% cellpadding=0
+                                                                                            cellspacing=0
+                                                                                            style="width:100% !important;">
+                                                                                            <tr>
+                                                                                                <td align=center>
+                                                                                                    <table class=t4
+                                                                                                        role=presentation
+                                                                                                        cellpadding=0
+                                                                                                        cellspacing=0
+                                                                                                        style="Margin-left:auto;Margin-right:auto;">
+                                                                                                        <tr>
+                                                                                                            <td width=160
+                                                                                                                class=t3
+                                                                                                                style="width:160px;">
+                                                                                                                <table
+                                                                                                                    class=t2
+                                                                                                                    role=presentation
+                                                                                                                    cellpadding=0
+                                                                                                                    cellspacing=0
+                                                                                                                    width=100%
+                                                                                                                    style="width:100%;">
+                                                                                                                    <tr>
+                                                                                                                        <td
+                                                                                                                            class=t1>
+                                                                                                                            <div
+                                                                                                                                style="font-size:0px;">
+                                                                                                                                <img class=t0
+                                                                                                                                    style="display:block;border:0;height:auto;width:100%;Margin:0;max-width:100%;"
+                                                                                                                                    width=160
+                                                                                                                                    height=34.316353887399465
+                                                                                                                                    alt=""
+                                                                                                                                    src="https://63d169ae-ca86-413d-ad95-3757c5d884a8.b-cdn.net/e/057afd58-8fe3-44c0-9dd1-bb8c2351ffe1/8e585437-510d-4534-a2f7-770b7040974d.png" />
+                                                                                                                            </div>
+                                                                                                                        </td>
+                                                                                                                    </tr>
+                                                                                                                </table>
+                                                                                                            </td>
+                                                                                                        </tr>
+                                                                                                    </table>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                            <tr>
+                                                                                                <td>
+                                                                                                    <div class=t5
+                                                                                                        style="mso-line-height-rule:exactly;mso-line-height-alt:40px;line-height:40px;font-size:1px;display:block;">
+                                                                                                        &nbsp;&nbsp;
+                                                                                                    </div>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                            <tr>
+                                                                                                <td align=center>
+                                                                                                    <table class=t10
+                                                                                                        role=presentation
+                                                                                                        cellpadding=0
+                                                                                                        cellspacing=0
+                                                                                                        style="Margin-left:auto;Margin-right:auto;">
+                                                                                                        <tr>
+                                                                                                            <td width=350
+                                                                                                                class=t9
+                                                                                                                style="width:350px;">
+                                                                                                                <table
+                                                                                                                    class=t8
+                                                                                                                    role=presentation
+                                                                                                                    cellpadding=0
+                                                                                                                    cellspacing=0
+                                                                                                                    width=100%
+                                                                                                                    style="width:100%;">
+                                                                                                                    <tr>
+                                                                                                                        <td
+                                                                                                                            class=t7>
+                                                                                                                            <p class=t6
+                                                                                                                                style="margin:0;Margin:0;font-family:Fira Sans,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif;line-height:30px;font-weight:500;font-style:normal;font-size:20px;text-decoration:none;text-transform:none;direction:ltr;color:#666666;text-align:center;mso-line-height-rule:exactly;mso-text-raise:3px;">
+                                                                                                                                You
+                                                                                                                                have
+                                                                                                                                a
+                                                                                                                                new
+                                                                                                                                support
+                                                                                                                                request
+                                                                                                                            </p>
+                                                                                                                        </td>
+                                                                                                                    </tr>
+                                                                                                                </table>
+                                                                                                            </td>
+                                                                                                        </tr>
+                                                                                                    </table>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                            <tr>
+                                                                                                <td>
+                                                                                                    <div class=t18
+                                                                                                        style="mso-line-height-rule:exactly;mso-line-height-alt:30px;line-height:30px;font-size:1px;display:block;">
+                                                                                                        &nbsp;&nbsp;
+                                                                                                    </div>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                            <tr>
+                                                                                                <td align=center>
+                                                                                                    <table class=t22
+                                                                                                        role=presentation
+                                                                                                        cellpadding=0
+                                                                                                        cellspacing=0
+                                                                                                        style="Margin-left:auto;Margin-right:auto;">
+                                                                                                        <tr>
+                                                                                                            <td width=520
+                                                                                                                class=t21
+                                                                                                                style="width:600px;">
+                                                                                                                <table
+                                                                                                                    class=t20
+                                                                                                                    role=presentation
+                                                                                                                    cellpadding=0
+                                                                                                                    cellspacing=0
+                                                                                                                    width=100%
+                                                                                                                    style="width:100%;">
+                                                                                                                    <tr>
+                                                                                                                        <td class=t19
+                                                                                                                            style="padding:0 50px 0 50px;">
+                                                                                                                            <p class=t17
+                                                                                                                                style="margin:0;Margin:0;font-family:Roboto,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif;line-height:24px;font-weight:400;font-style:normal;font-size:18px;text-decoration:none;text-transform:none;direction:ltr;color:#333333;text-align:left;mso-line-height-rule:exactly;mso-text-raise:2px;">
+                                                                                                                                <span
+                                                                                                                                    class=t11
+                                                                                                                                    style="margin:0;Margin:0;font-weight:700;mso-line-height-rule:exactly;">Title</span>:
+                                                                                                                                {{.Title}}<br /><span
+                                                                                                                                    class=t12
+                                                                                                                                    style="margin:0;Margin:0;font-weight:700;mso-line-height-rule:exactly;">Type</span>:
+                                                                                                                                {{.Type}}<br /><span
+                                                                                                                                    class=t13
+                                                                                                                                    style="margin:0;Margin:0;font-weight:700;mso-line-height-rule:exactly;">User</span>:
+                                                                                                                                <span
+                                                                                                                                    class=t14
+                                                                                                                                    style="margin:0;Margin:0;color:#0000FF;mso-line-height-rule:exactly;">{{.Email}}</span><br /><span
+                                                                                                                                    class=t15
+                                                                                                                                    style="margin:0;Margin:0;font-weight:700;mso-line-height-rule:exactly;">Organization</span>:
+                                                                                                                                {{.Organization}}<br /><br /><span
+                                                                                                                                    class=t16
+                                                                                                                                    style="margin:0;Margin:0;font-weight:700;mso-line-height-rule:exactly;">Description</span>:<br />{{.Description}}
+                                                                                                                            </p>
+                                                                                                                        </td>
+                                                                                                                    </tr>
+                                                                                                                </table>
+                                                                                                            </td>
+                                                                                                        </tr>
+                                                                                                    </table>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                        </table>
+                                                                                    </td>
+                                                                                </tr>
+                                                                            </table>
+                                                                        </td>
+                                                                        <td></td>
+                                                                    </tr>
+                                                                </table>
+                                                            </div>
+                                                        </div>
+                                                    </td>
+                                                </tr>
+                                            </table>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <div class=t35
+                                    style="mso-line-height-rule:exactly;mso-line-height-alt:70px;line-height:70px;font-size:1px;display:block;">
+                                    &nbsp;&nbsp;</div>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align=center>
+                                <table class=t59 role=presentation cellpadding=0 cellspacing=0
+                                    style="Margin-left:auto;Margin-right:auto;">
+                                    <tr>
+                                        <td width=600 class=t58 style="width:600px;">
+                                            <table class=t57 role=presentation cellpadding=0 cellspacing=0 width=100%
+                                                style="width:100%;">
+                                                <tr>
+                                                    <td class=t56 style="background-color:transparent;">
+                                                        <div class=t55 style="width:100%;text-align:left;">
+                                                            <div class=t54 style="display:inline-block;">
+                                                                <table class=t53 role=presentation cellpadding=0
+                                                                    cellspacing=0 align=left valign=top>
+                                                                    <tr class=t52>
+                                                                        <td></td>
+                                                                        <td class=t51 width=600 valign=top>
+                                                                            <table role=presentation width=100%
+                                                                                cellpadding=0 cellspacing=0 class=t50
+                                                                                style="width:100%;">
+                                                                                <tr>
+                                                                                    <td class=t49
+                                                                                        style="background-color:transparent;padding:40px 0 40px 0;">
+                                                                                        <table role=presentation
+                                                                                            width=100% cellpadding=0
+                                                                                            cellspacing=0
+                                                                                            style="width:100% !important;">
+                                                                                            <tr>
+                                                                                                <td align=center>
+                                                                                                    <table class=t41
+                                                                                                        role=presentation
+                                                                                                        cellpadding=0
+                                                                                                        cellspacing=0
+                                                                                                        style="Margin-left:auto;Margin-right:auto;">
+                                                                                                        <tr>
+                                                                                                            <td width=350
+                                                                                                                class=t40
+                                                                                                                style="width:350px;">
+                                                                                                                <table
+                                                                                                                    class=t39
+                                                                                                                    role=presentation
+                                                                                                                    cellpadding=0
+                                                                                                                    cellspacing=0
+                                                                                                                    width=100%
+                                                                                                                    style="width:100%;">
+                                                                                                                    <tr>
+                                                                                                                        <td
+                                                                                                                            class=t38>
+                                                                                                                            <p class=t37
+                                                                                                                                style="margin:0;Margin:0;font-family:Fira Sans,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif;line-height:19px;font-weight:400;font-style:normal;font-size:12px;text-decoration:none;text-transform:none;direction:ltr;color:#BBBBBB;text-align:center;mso-line-height-rule:exactly;mso-text-raise:2px;">
+                                                                                                                                <span
+                                                                                                                                    class=t36
+                                                                                                                                    style="margin:0;Margin:0;font-weight:700;mso-line-height-rule:exactly;">Vocdoni</span>
+                                                                                                                                is
+                                                                                                                                a
+                                                                                                                                blockchain-based
+                                                                                                                                voting
+                                                                                                                                platform
+                                                                                                                                designed
+                                                                                                                                to
+                                                                                                                                ensure
+                                                                                                                                secure,
+                                                                                                                                transparent,
+                                                                                                                                and
+                                                                                                                                tamper-proof
+                                                                                                                                digital
+                                                                                                                                voting.
+                                                                                                                                It
+                                                                                                                                leverages
+                                                                                                                                decentralized
+                                                                                                                                technologies
+                                                                                                                                to
+                                                                                                                                provide
+                                                                                                                                scalable
+                                                                                                                                and
+                                                                                                                                verifiable
+                                                                                                                                election
+                                                                                                                                processes
+                                                                                                                                for
+                                                                                                                                organizations
+                                                                                                                                and
+                                                                                                                                communities.
+                                                                                                                                Vocdoni
+                                                                                                                                aims
+                                                                                                                                to
+                                                                                                                                enhance
+                                                                                                                                democratic
+                                                                                                                                participation
+                                                                                                                                through
+                                                                                                                                innovative
+                                                                                                                                and
+                                                                                                                                accessible
+                                                                                                                                voting
+                                                                                                                                solutions.
+                                                                                                                            </p>
+                                                                                                                        </td>
+                                                                                                                    </tr>
+                                                                                                                </table>
+                                                                                                            </td>
+                                                                                                        </tr>
+                                                                                                    </table>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                            <tr>
+                                                                                                <td>
+                                                                                                    <div class=t42
+                                                                                                        style="mso-line-height-rule:exactly;mso-line-height-alt:20px;line-height:20px;font-size:1px;display:block;">
+                                                                                                        &nbsp;&nbsp;
+                                                                                                    </div>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                            <tr>
+                                                                                                <td align=center>
+                                                                                                    <table class=t48
+                                                                                                        role=presentation
+                                                                                                        cellpadding=0
+                                                                                                        cellspacing=0
+                                                                                                        style="Margin-left:auto;Margin-right:auto;">
+                                                                                                        <tr>
+                                                                                                            <td width=350
+                                                                                                                class=t47
+                                                                                                                style="width:350px;">
+                                                                                                                <table
+                                                                                                                    class=t46
+                                                                                                                    role=presentation
+                                                                                                                    cellpadding=0
+                                                                                                                    cellspacing=0
+                                                                                                                    width=100%
+                                                                                                                    style="width:100%;">
+                                                                                                                    <tr>
+                                                                                                                        <td
+                                                                                                                            class=t45>
+                                                                                                                            <p class=t44
+                                                                                                                                style="margin:0;Margin:0;font-family:Fira Sans,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,sans-serif;line-height:19px;font-weight:400;font-style:normal;font-size:12px;text-decoration:none;text-transform:none;direction:ltr;color:#BBBBBB;text-align:center;mso-line-height-rule:exactly;mso-text-raise:2px;">
+                                                                                                                                <span
+                                                                                                                                    class=t43
+                                                                                                                                    style="margin:0;Margin:0;font-style:italic;mso-line-height-rule:exactly;">Vocdoni
+                                                                                                                                    Association.
+                                                                                                                                    All
+                                                                                                                                    rights
+                                                                                                                                    reserved</span>
+                                                                                                                            </p>
+                                                                                                                        </td>
+                                                                                                                    </tr>
+                                                                                                                </table>
+                                                                                                            </td>
+                                                                                                        </tr>
+                                                                                                    </table>
+                                                                                                </td>
+                                                                                            </tr>
+                                                                                        </table>
+                                                                                    </td>
+                                                                                </tr>
+                                                                            </table>
+                                                                        </td>
+                                                                        <td></td>
+                                                                    </tr>
+                                                                </table>
+                                                            </div>
+                                                        </div>
+                                                    </td>
+                                                </tr>
+                                            </table>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </tr>
+                    </table>
+                </td>
+            </tr>
+        </table>
+    </div>
+    <div class="gmail-fix" style="display: none; white-space: nowrap; font: 15px courier; line-height: 0;">&nbsp; &nbsp;
+        &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
+        &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 7747037b..1c2ed2d4 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -37,6 +37,18 @@ definitions:
         description: Number of participants added
         type: integer
     type: object
+  apicommon.CreateOrganizationTicketRequest:
+    properties:
+      description:
+        description: Body of the ticket
+        type: string
+      title:
+        description: Title of the ticket
+        type: string
+      type:
+        description: Type of the ticket to create (definded externally)
+        type: string
+    type: object
   apicommon.CreateProcessBundleRequest:
     properties:
       censusID:
@@ -22844,6 +22856,52 @@ paths:
       summary: Get organization subscription
       tags:
       - organizations
+  /organizations/{address}/ticket:
+    post:
+      consumes:
+      - application/json
+      description: Create a new ticket for an organization. The user must be a member
+        of the organization with any role.
+      parameters:
+      - description: Organization address
+        in: path
+        name: address
+        required: true
+        type: string
+      - description: Ticket request information
+        in: body
+        name: request
+        required: true
+        schema:
+          $ref: '#/definitions/apicommon.CreateOrganizationTicketRequest'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            type: string
+        "400":
+          description: Invalid input data
+          schema:
+            $ref: '#/definitions/errors.Error'
+        "401":
+          description: Unauthorized
+          schema:
+            $ref: '#/definitions/errors.Error'
+        "404":
+          description: Organization not found
+          schema:
+            $ref: '#/definitions/errors.Error'
+        "500":
+          description: Internal server error
+          schema:
+            $ref: '#/definitions/errors.Error'
+      security:
+      - BearerAuth: []
+      summary: Create a new ticket for an organization
+      tags:
+      - organizations
   /organizations/{orgAddress}/meta:
     delete:
       consumes:
@@ -22984,8 +23042,11 @@ paths:
     put:
       consumes:
       - application/json
-      description: Updates existing or adds new key/value pairs in the meta information
-        of an organization.
+      description: |-
+        Updates existing or adds new key/value pairs in the meta information of an organization.
+        Has only one layer of depth. If a second layer document is provided, for example meta.doc = [a,b,c],
+        the entire document will be updated.
+        Requires Manager/Admin role.
       parameters:
       - description: Organization address
         in: path
diff --git a/notifications/mailtemplates/definitions.go b/notifications/mailtemplates/definitions.go
index 4df7097e..873446cd 100644
--- a/notifications/mailtemplates/definitions.go
+++ b/notifications/mailtemplates/definitions.go
@@ -53,3 +53,20 @@ You can also use this link to join the organization: {{.Link}}`,
 	},
 	WebAppURI: "/account/invite",
 }
+
+// PlainNotification is a notification that does not require any template
+var SupportNotification = MailTemplate{
+	File: "support",
+	Placeholder: notifications.Notification{
+		Subject: "New {{.Type}} Ticket from {{.Email}}: {{.Title}}",
+		PlainBody: `You have a new support request:
+
+		Title: {{.Title}}
+		Type: {{.Type}}
+		User: {{.Email}}
+		Organization: {{.Organization}}
+
+		Description:
+		{{.Description}}`,
+	},
+}
diff --git a/notifications/mailtemplates/templates.go b/notifications/mailtemplates/templates.go
index 8f13962d..9cdc9e27 100644
--- a/notifications/mailtemplates/templates.go
+++ b/notifications/mailtemplates/templates.go
@@ -77,8 +77,6 @@ func (mt MailTemplate) ExecTemplate(data any) (*notifications.Notification, erro
 	if err != nil {
 		return nil, err
 	}
-	// set the mail subject
-	n.Subject = mt.Placeholder.Subject
 	// parse the html template file
 	content, err := root.Assets.ReadFile(path)
 	if err != nil {
@@ -122,5 +120,19 @@ func (mt MailTemplate) ExecPlain(data any) (*notifications.Notification, error)
 		// return the notification with the plain body filled with the data
 		n.PlainBody = buf.String()
 	}
+	if mt.Placeholder.Subject != "" {
+		// parse the placeholder subject template
+		tmpl, err := texttemplate.New("subject").Parse(mt.Placeholder.Subject)
+		if err != nil {
+			return nil, err
+		}
+		// inflate the template with the data
+		buf := new(bytes.Buffer)
+		if err := tmpl.Execute(buf, data); err != nil {
+			return nil, err
+		}
+		// return the notification with the subject filled with the data
+		n.Subject = buf.String()
+	}
 	return n, nil
 }
diff --git a/notifications/notifications.go b/notifications/notifications.go
index 527475c9..9d972fd6 100644
--- a/notifications/notifications.go
+++ b/notifications/notifications.go
@@ -28,6 +28,7 @@ type Notification struct {
 	Body           string `json:"body"`
 	PlainBody      string `json:"plainBody"`
 	EnableTracking bool   `json:"enableTracking"`
+	ReplyTo        string `json:"replyTo"`
 }
 
 // NotificationService is the interface that must be implemented by any
diff --git a/notifications/smtp/smtp.go b/notifications/smtp/smtp.go
index 9698c322..1f72e53a 100644
--- a/notifications/smtp/smtp.go
+++ b/notifications/smtp/smtp.go
@@ -102,6 +102,13 @@ func (se *Email) composeBody(notification *notifications.Notification) ([]byte,
 	boundary := "----=_Part_0_123456789.123456789"
 	headers.WriteString(fmt.Sprintf("From: %s\r\n", se.config.FromAddress))
 	headers.WriteString(fmt.Sprintf("To: %s\r\n", to.String()))
+	if notification.ReplyTo != "" {
+		replyToAddress, err := mail.ParseAddress(notification.ReplyTo)
+		if err != nil {
+			return nil, fmt.Errorf("could not parse reply-to email: %v", err)
+		}
+		headers.WriteString(fmt.Sprintf("Reply-To: %s\r\n", replyToAddress.String()))
+	}
 	headers.WriteString(fmt.Sprintf("Subject: %s\r\n", notification.Subject))
 	if !notification.EnableTracking {
 		headers.WriteString(fmt.Sprintf("X-SMTPAPI: %s\r\n", disableTrackingFilter))


Reviews:
user: login: altergui
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T08:22:52Z
user: login: emmdim
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:54:43Z
user: login: altergui
body: LGTM, missing just one fix (and regenerate swagger.yml) but i'm approving already
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T08:50:01Z
user: login: emmdim
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T09:09:12Z

Review comments:
diff_hunk: @@ -853,3 +853,14 @@ type OAuthServiceAddressResponse struct {
 	// The address of the OAuth service signer
 	Address string `json:"address"`
 }
+
+type CreateOrganizationTicketRequest struct {
+	// Type of te ticket to create (definde externally)
path: api/apicommon/types.go
user: login: altergui
body: the 
defined
created_at: 2025-05-21T08:15:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1046,6 +1047,60 @@ Deletes multiple participants from an organization by their participant IDs. Req
 | `400` | `40011` | `no organization provided` |
 | `500` | `50002` | `internal server error` |
 
+### 🎫 Create Organization Ticket
+
+* **Path** `/organizations/{address}/tickets`
+* **Method** `POST`
+* **Headers**
+  * `Authentication: Bearer <user_token>`
+* **Request body**
+```json
+{
+  "type": "support",
+  "title": "Need help with voting process",
+  "description": "I'm having trouble setting up a new voting process. Can you help?"
+}
+```
+
+* **Description**
+Creates a new support ticket for the organization. The user must be a member of the organization with any role. The ticket is sent to the support team via email.
+
+* **Errors**
+
+| HTTP Status | Error code | Message |
+|:---:|:---:|:---|
+| `401` | `40001` | `user not authorized` |
+| `400` | `40004` | `malformed JSON body` |
+| `400` | `40011` | `no organization provided` |
+| `500` | `50002` | `internal server error` |
+
+### 🎫 Create Organization Ticket
+
+* **Path** `/organizations/{address}/tickets`
+* **Method** `POST`
+* **Headers**
+  * `Authentication: Bearer <user_token>`
+* **Request body**
+```json
+{
+  "type": "support",
+  "title": "Need help with voting process",
+  "description": "I'm having trouble setting up a new voting process. Can you help?"
+}
+```
+
+* **Description**
+Creates a new support ticket for the organization. The user must be a member of the organization with any role. The ticket is sent to the support team via email.
+
+* **Errors**
+
+| HTTP Status | Error code | Message |
+|:---:|:---:|:---|
+| `401` | `40001` | `user not authorized` |
+| `400` | `40004` | `malformed JSON body` |
+| `400` | `40011` | `no organization provided` |
+| `500` | `50002` | `internal server error` |
+
path: api/docs.md
user: login: altergui
body: why twice? isn't this is exactly the same as lines 1050-1076?
created_at: 2025-05-21T08:17:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,6 +72,8 @@ const (
 	organizationDeleteParticipantsEndpoint = "/organizations/{address}/participants"
 	// POST/PUT/GET/DELETE /organizations/{address}/meta to add/set/get/delete the organization metadata
 	organizationMetaEndpoint = "/organizations/{address}/meta"
+	// POST /organizations/{address}/ticket to create a new ticket to our support system
+	organizationCreateTicketEndpoint = "/organizations/{address}/tickets"
path: api/routes.go
user: login: altergui
body: `POST /organizations/{address}/ticket` (no plural) sounds more reasonable to me 

(unless there are plans to implement a `GET /organizations/{address}/tickets` but i don't think that's the case, right?)
created_at: 2025-05-21T08:19:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,6 +72,8 @@ const (
 	organizationDeleteParticipantsEndpoint = "/organizations/{address}/participants"
 	// POST/PUT/GET/DELETE /organizations/{address}/meta to add/set/get/delete the organization metadata
 	organizationMetaEndpoint = "/organizations/{address}/meta"
+	// POST /organizations/{address}/ticket to create a new ticket to our support system
+	organizationCreateTicketEndpoint = "/organizations/{address}/tickets"
path: api/routes.go
user: login: altergui
body: in fact the comment on top says `ticket`
created_at: 2025-05-21T08:20:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1046,6 +1047,60 @@ Deletes multiple participants from an organization by their participant IDs. Req
 | `400` | `40011` | `no organization provided` |
 | `500` | `50002` | `internal server error` |
 
+### 🎫 Create Organization Ticket
+
+* **Path** `/organizations/{address}/tickets`
+* **Method** `POST`
+* **Headers**
+  * `Authentication: Bearer <user_token>`
+* **Request body**
+```json
+{
+  "type": "support",
+  "title": "Need help with voting process",
+  "description": "I'm having trouble setting up a new voting process. Can you help?"
+}
+```
+
+* **Description**
+Creates a new support ticket for the organization. The user must be a member of the organization with any role. The ticket is sent to the support team via email.
+
+* **Errors**
+
+| HTTP Status | Error code | Message |
+|:---:|:---:|:---|
+| `401` | `40001` | `user not authorized` |
+| `400` | `40004` | `malformed JSON body` |
+| `400` | `40011` | `no organization provided` |
+| `500` | `50002` | `internal server error` |
+
+### 🎫 Create Organization Ticket
+
+* **Path** `/organizations/{address}/tickets`
+* **Method** `POST`
+* **Headers**
+  * `Authentication: Bearer <user_token>`
+* **Request body**
+```json
+{
+  "type": "support",
+  "title": "Need help with voting process",
+  "description": "I'm having trouble setting up a new voting process. Can you help?"
+}
+```
+
+* **Description**
+Creates a new support ticket for the organization. The user must be a member of the organization with any role. The ticket is sent to the support team via email.
+
+* **Errors**
+
+| HTTP Status | Error code | Message |
+|:---:|:---:|:---|
+| `401` | `40001` | `user not authorized` |
+| `400` | `40004` | `malformed JSON body` |
+| `400` | `40011` | `no organization provided` |
+| `500` | `50002` | `internal server error` |
+
path: api/docs.md
user: login: emmdim
body: Oops. This was an error introduced when rebasing with main.
created_at: 2025-05-21T13:54:43Z
author_association: MEMBER
diff_hunk: @@ -711,3 +711,79 @@ func (a *API) organizationCensusesHandler(w http.ResponseWriter, r *http.Request
 	}
 	apicommon.HTTPWriteJSON(w, result)
 }
+
+// organizationCreateTicket godoc
+//
+//	@Summary		Create a new ticket for an organization
+//	@Description	Create a new ticket for an organization. The user must be a member of the organization with any role.
+//	@Tags			organizations
+//	@Accept			json
+//	@Produce		json
+//	@Security		BearerAuth
+//	@Param			address	path		string										true	"Organization address"
+//	@Param			request	body		apicommon.CreateOrganizationTicketRequest	true	"Ticket request information"
+//	@Success		200		{string}	string										"OK"
+//	@Failure		400		{object}	errors.Error								"Invalid input data"
+//	@Failure		401		{object}	errors.Error								"Unauthorized"
+//	@Failure		404		{object}	errors.Error								"Organization not found"
+//	@Failure		500		{object}	errors.Error								"Internal server error"
+//	@Router			/organizations/{address}/tickets [post]
path: api/organizations.go
user: login: altergui
body: missed this one, should be ticket
created_at: 2025-05-22T08:48:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -711,3 +711,79 @@ func (a *API) organizationCensusesHandler(w http.ResponseWriter, r *http.Request
 	}
 	apicommon.HTTPWriteJSON(w, result)
 }
+
+// organizationCreateTicket godoc
+//
+//	@Summary		Create a new ticket for an organization
+//	@Description	Create a new ticket for an organization. The user must be a member of the organization with any role.
+//	@Tags			organizations
+//	@Accept			json
+//	@Produce		json
+//	@Security		BearerAuth
+//	@Param			address	path		string										true	"Organization address"
+//	@Param			request	body		apicommon.CreateOrganizationTicketRequest	true	"Ticket request information"
+//	@Success		200		{string}	string										"OK"
+//	@Failure		400		{object}	errors.Error								"Invalid input data"
+//	@Failure		401		{object}	errors.Error								"Unauthorized"
+//	@Failure		404		{object}	errors.Error								"Organization not found"
+//	@Failure		500		{object}	errors.Error								"Internal server error"
+//	@Router			/organizations/{address}/tickets [post]
path: api/organizations.go
user: login: emmdim
body: Good catch
created_at: 2025-05-22T09:09:12Z
author_association: MEMBER

PR comments:
user: login: emmdim
created_at: 2025-05-15T15:22:41Z
body: ToD:
* [x]  HTML template
* [x] Apply template exec for Subject
* [x] confirm that sending to the support google group works

author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-05-16T14:45:57Z
body: This PR introduces the following changes in the API documentation:
```diff
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 8e71dae..0f1b9ce 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -421,6 +421,9 @@ definitions:
       personalization:
         description: Whether UI personalization is available
         type: boolean
+      phoneSupport:
+        description: Whether eligible for phone support
+        type: boolean
       smsNotification:
         description: Whether SMS notifications are available
         type: boolean
@@ -677,6 +680,8 @@ definitions:
         type: boolean
       personalization:
         type: boolean
+      phoneSupport:
+        type: boolean
       smsNotification:
         type: boolean
       whiteLabel:

```
<!-- Sticky Pull Request Commentdiff -->
author_association: NONE
user: login: coveralls
created_at: 2025-05-21T13:57:13Z
body: ## Pull Request Test Coverage Report for [Build 15182580943](https://coveralls.io/builds/73794295)


### Details

* **11** of **73**   **(15.07%)**  changed or added relevant lines in **4** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.4%**) to **52.219%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [notifications/mailtemplates/templates.go](https://coveralls.io/builds/73794295/source?filename=notifications%2Fmailtemplates%2Ftemplates.go#L127) | 7 | 11 | 63.64%
| [notifications/smtp/smtp.go](https://coveralls.io/builds/73794295/source?filename=notifications%2Fsmtp%2Fsmtp.go#L106) | 1 | 6 | 16.67%
| [api/organizations.go](https://coveralls.io/builds/73794295/source?filename=api%2Forganizations.go#L731) | 0 | 53 | 0.0%
<!-- | **Total:** | **11** | **73** | **15.07%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/73794295/badge)](https://coveralls.io/builds/73794295) |
| :-- | --: |
| Change from base [Build 15134521741](https://coveralls.io/builds/73754047): |  -0.4% |
| Covered Lines: | 3966 |
| Relevant Lines: | 7595 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE