diff --git a/qqq-backend-core/pom.xml b/qqq-backend-core/pom.xml
index 26a4848f..2b6fb128 100644
--- a/qqq-backend-core/pom.xml
+++ b/qqq-backend-core/pom.xml
@@ -186,6 +186,12 @@
2.23.0
+
+ com.sun.mail
+ jakarta.mail
+ 2.0.1
+
+
org.apache.maven.plugins
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/messaging/SendMessageAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/messaging/SendMessageAction.java
new file mode 100644
index 00000000..f0cccf1e
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/messaging/SendMessageAction.java
@@ -0,0 +1,64 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.actions.messaging;
+
+
+import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
+import com.kingsrook.qqq.backend.core.modules.messaging.MessagingProviderInterface;
+import com.kingsrook.qqq.backend.core.modules.messaging.QMessagingProviderDispatcher;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SendMessageAction extends AbstractQActionFunction
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public SendMessageOutput execute(SendMessageInput input) throws QException
+ {
+ if(!StringUtils.hasContent(input.getMessagingProviderName()))
+ {
+ throw (new QException("Messaging provider name was not given in SendMessageInput."));
+ }
+
+ QMessagingProviderMetaData messagingProvider = QContext.getQInstance().getMessagingProvider(input.getMessagingProviderName());
+ if(messagingProvider == null)
+ {
+ throw (new QException("Messaging provider named [" + input.getMessagingProviderName() + "] was not found in this QInstance."));
+ }
+
+ MessagingProviderInterface messagingProviderInterface = new QMessagingProviderDispatcher().getMessagingProviderInterface(messagingProvider.getType());
+
+ return (messagingProviderInterface.sendMessage(input));
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Attachment.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Attachment.java
new file mode 100644
index 00000000..f3720fac
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Attachment.java
@@ -0,0 +1,95 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class Attachment
+{
+ private byte[] contents;
+ private String name;
+
+
+
+ /*******************************************************************************
+ ** Getter for contents
+ *******************************************************************************/
+ public byte[] getContents()
+ {
+ return (this.contents);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for contents
+ *******************************************************************************/
+ public void setContents(byte[] contents)
+ {
+ this.contents = contents;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for contents
+ *******************************************************************************/
+ public Attachment withContents(byte[] contents)
+ {
+ this.contents = contents;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for name
+ *******************************************************************************/
+ public String getName()
+ {
+ return (this.name);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for name
+ *******************************************************************************/
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ *******************************************************************************/
+ public Attachment withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Content.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Content.java
new file mode 100644
index 00000000..07d9cab0
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Content.java
@@ -0,0 +1,95 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class Content
+{
+ private String body;
+ private ContentRole contentRole;
+
+
+
+ /*******************************************************************************
+ ** Getter for body
+ *******************************************************************************/
+ public String getBody()
+ {
+ return (this.body);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for body
+ *******************************************************************************/
+ public void setBody(String body)
+ {
+ this.body = body;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for body
+ *******************************************************************************/
+ public Content withBody(String body)
+ {
+ this.body = body;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for contentRole
+ *******************************************************************************/
+ public ContentRole getContentRole()
+ {
+ return (this.contentRole);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for contentRole
+ *******************************************************************************/
+ public void setContentRole(ContentRole contentRole)
+ {
+ this.contentRole = contentRole;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for contentRole
+ *******************************************************************************/
+ public Content withContentRole(ContentRole contentRole)
+ {
+ this.contentRole = contentRole;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/ContentRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/ContentRole.java
new file mode 100644
index 00000000..d18dd03f
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/ContentRole.java
@@ -0,0 +1,39 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface ContentRole
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ enum Default implements ContentRole
+ {
+ DEFAULT
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/MultiParty.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/MultiParty.java
new file mode 100644
index 00000000..f736b3e7
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/MultiParty.java
@@ -0,0 +1,92 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class MultiParty extends Party
+{
+ private List partyList;
+
+
+
+ /*******************************************************************************
+ ** Getter for partyList
+ *******************************************************************************/
+ public List getPartyList()
+ {
+ return (this.partyList);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for partyList
+ *******************************************************************************/
+ public void setPartyList(List partyList)
+ {
+ this.partyList = partyList;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for partyList
+ *******************************************************************************/
+ public MultiParty withPartyList(List partyList)
+ {
+ this.partyList = partyList;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public MultiParty withParty(Party party)
+ {
+ addParty(party);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addParty(Party party)
+ {
+ if(this.partyList == null)
+ {
+ this.partyList = new ArrayList<>();
+ }
+ this.partyList.add(party);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Party.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Party.java
new file mode 100644
index 00000000..13b378ba
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/Party.java
@@ -0,0 +1,127 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class Party
+{
+ private String label;
+ private String address;
+ private PartyRole role;
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return (this.label);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for label
+ *******************************************************************************/
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for label
+ *******************************************************************************/
+ public Party withLabel(String label)
+ {
+ this.label = label;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for address
+ *******************************************************************************/
+ public String getAddress()
+ {
+ return (this.address);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for address
+ *******************************************************************************/
+ public void setAddress(String address)
+ {
+ this.address = address;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for address
+ *******************************************************************************/
+ public Party withAddress(String address)
+ {
+ this.address = address;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for role
+ *******************************************************************************/
+ public PartyRole getRole()
+ {
+ return (this.role);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for role
+ *******************************************************************************/
+ public void setRole(PartyRole role)
+ {
+ this.role = role;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for role
+ *******************************************************************************/
+ public Party withRole(PartyRole role)
+ {
+ this.role = role;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/PartyRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/PartyRole.java
new file mode 100644
index 00000000..d70461b5
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/PartyRole.java
@@ -0,0 +1,39 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface PartyRole
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ enum Default implements PartyRole
+ {
+ DEFAULT
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/SendMessageInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/SendMessageInput.java
new file mode 100644
index 00000000..f502dfa3
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/SendMessageInput.java
@@ -0,0 +1,278 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SendMessageInput extends AbstractActionInput
+{
+ private String messagingProviderName;
+ private Party to;
+ private Party from;
+ private String subject;
+ private List contentList;
+ private List attachmentList;
+
+
+
+ /*******************************************************************************
+ ** Getter for to
+ *******************************************************************************/
+ public Party getTo()
+ {
+ return (this.to);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for to
+ *******************************************************************************/
+ public void setTo(Party to)
+ {
+ this.to = to;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for to
+ *******************************************************************************/
+ public SendMessageInput withTo(Party to)
+ {
+ this.to = to;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for from
+ *******************************************************************************/
+ public Party getFrom()
+ {
+ return (this.from);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for from
+ *******************************************************************************/
+ public void setFrom(Party from)
+ {
+ this.from = from;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for from
+ *******************************************************************************/
+ public SendMessageInput withFrom(Party from)
+ {
+ this.from = from;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for subject
+ *******************************************************************************/
+ public String getSubject()
+ {
+ return (this.subject);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for subject
+ *******************************************************************************/
+ public void setSubject(String subject)
+ {
+ this.subject = subject;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for subject
+ *******************************************************************************/
+ public SendMessageInput withSubject(String subject)
+ {
+ this.subject = subject;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for contentList
+ *******************************************************************************/
+ public List getContentList()
+ {
+ return (this.contentList);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for contentList
+ *******************************************************************************/
+ public void setContentList(List contentList)
+ {
+ this.contentList = contentList;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for contentList
+ *******************************************************************************/
+ public SendMessageInput withContentList(List contentList)
+ {
+ this.contentList = contentList;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for attachmentList
+ *******************************************************************************/
+ public List getAttachmentList()
+ {
+ return (this.attachmentList);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for attachmentList
+ *******************************************************************************/
+ public void setAttachmentList(List attachmentList)
+ {
+ this.attachmentList = attachmentList;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for attachmentList
+ *******************************************************************************/
+ public SendMessageInput withAttachmentList(List attachmentList)
+ {
+ this.attachmentList = attachmentList;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public SendMessageInput withContent(Content content)
+ {
+ addContent(content);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addContent(Content content)
+ {
+ if(this.contentList == null)
+ {
+ this.contentList = new ArrayList<>();
+ }
+ this.contentList.add(content);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public SendMessageInput withAttachment(Attachment attachment)
+ {
+ addAttachment(attachment);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addAttachment(Attachment attachment)
+ {
+ if(this.attachmentList == null)
+ {
+ this.attachmentList = new ArrayList<>();
+ }
+ this.attachmentList.add(attachment);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for messagingProviderName
+ *******************************************************************************/
+ public String getMessagingProviderName()
+ {
+ return (this.messagingProviderName);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for messagingProviderName
+ *******************************************************************************/
+ public void setMessagingProviderName(String messagingProviderName)
+ {
+ this.messagingProviderName = messagingProviderName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for messagingProviderName
+ *******************************************************************************/
+ public SendMessageInput withMessagingProviderName(String messagingProviderName)
+ {
+ this.messagingProviderName = messagingProviderName;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/SendMessageOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/SendMessageOutput.java
new file mode 100644
index 00000000..032064a7
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/SendMessageOutput.java
@@ -0,0 +1,33 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging;
+
+
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SendMessageOutput extends AbstractActionOutput
+{
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/email/EmailContentRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/email/EmailContentRole.java
new file mode 100644
index 00000000..9c8bf0d3
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/email/EmailContentRole.java
@@ -0,0 +1,35 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging.email;
+
+
+import com.kingsrook.qqq.backend.core.model.actions.messaging.ContentRole;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public enum EmailContentRole implements ContentRole
+{
+ TEXT,
+ HTML
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/email/EmailPartyRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/email/EmailPartyRole.java
new file mode 100644
index 00000000..4c7c8e6f
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/messaging/email/EmailPartyRole.java
@@ -0,0 +1,38 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.actions.messaging.email;
+
+
+import com.kingsrook.qqq.backend.core.model.actions.messaging.PartyRole;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public enum EmailPartyRole implements PartyRole
+{
+ TO,
+ CC,
+ BCC,
+ FROM,
+ REPLY_TO
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
index 255e4c7e..1c5ecbe6 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
@@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
@@ -78,6 +79,7 @@ public class QInstance
private QAuthenticationMetaData authentication = null;
private QBrandingMetaData branding = null;
private Map automationProviders = new HashMap<>();
+ private Map messagingProviders = new HashMap<>();
////////////////////////////////////////////////////////////////////////////////////////////
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
@@ -739,6 +741,53 @@ public class QInstance
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addMessagingProvider(QMessagingProviderMetaData messagingProvider)
+ {
+ String name = messagingProvider.getName();
+ if(this.messagingProviders.containsKey(name))
+ {
+ throw (new IllegalArgumentException("Attempted to add a second messagingProvider with name: " + name));
+ }
+ this.messagingProviders.put(name, messagingProvider);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QMessagingProviderMetaData getMessagingProvider(String name)
+ {
+ return (this.messagingProviders.get(name));
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for messagingProviders
+ **
+ *******************************************************************************/
+ public Map getMessagingProviders()
+ {
+ return messagingProviders;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for messagingProviders
+ **
+ *******************************************************************************/
+ public void setMessagingProviders(Map messagingProviders)
+ {
+ this.messagingProviders = messagingProviders;
+ }
+
+
+
/*******************************************************************************
** Getter for hasBeenValidated
**
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/QMessagingProviderMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/QMessagingProviderMetaData.java
new file mode 100644
index 00000000..116104ea
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/QMessagingProviderMetaData.java
@@ -0,0 +1,110 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.messaging;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
+
+
+/*******************************************************************************
+ ** Base class for qqq messaging-providers. e.g., a connection to an outbound
+ ** email service, or, for example, Slack.
+ *******************************************************************************/
+public class QMessagingProviderMetaData implements TopLevelMetaDataInterface
+{
+ private String name;
+ private String type;
+
+
+
+ /*******************************************************************************
+ ** Getter for name
+ *******************************************************************************/
+ public String getName()
+ {
+ return (this.name);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for name
+ *******************************************************************************/
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ *******************************************************************************/
+ public QMessagingProviderMetaData withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for type
+ *******************************************************************************/
+ public String getType()
+ {
+ return (this.type);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for type
+ *******************************************************************************/
+ public void setType(String type)
+ {
+ this.type = type;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for type
+ *******************************************************************************/
+ public QMessagingProviderMetaData withType(String type)
+ {
+ this.type = type;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public void addSelfToInstance(QInstance qInstance)
+ {
+ qInstance.addMessagingProvider(this);
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProvider.java
new file mode 100644
index 00000000..df920919
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProvider.java
@@ -0,0 +1,56 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
+import com.kingsrook.qqq.backend.core.modules.messaging.MessagingProviderInterface;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class EmailMessagingProvider implements MessagingProviderInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public String getType()
+ {
+ return (EmailMessagingProviderMetaData.TYPE);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException
+ {
+ return new SendEmailAction().sendMessage(sendMessageInput);
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProviderMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProviderMetaData.java
new file mode 100644
index 00000000..7fdb931a
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProviderMetaData.java
@@ -0,0 +1,116 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
+import com.kingsrook.qqq.backend.core.modules.messaging.QMessagingProviderDispatcher;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class EmailMessagingProviderMetaData extends QMessagingProviderMetaData
+{
+ private String smtpServer;
+ private String smtpPort;
+
+ public static final String TYPE = "EMAIL";
+
+ static
+ {
+ QMessagingProviderDispatcher.registerMessagingProvider(new EmailMessagingProvider());
+ }
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public EmailMessagingProviderMetaData()
+ {
+ super();
+ setType(TYPE);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for smtpServer
+ *******************************************************************************/
+ public String getSmtpServer()
+ {
+ return (this.smtpServer);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for smtpServer
+ *******************************************************************************/
+ public void setSmtpServer(String smtpServer)
+ {
+ this.smtpServer = smtpServer;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for smtpServer
+ *******************************************************************************/
+ public EmailMessagingProviderMetaData withSmtpServer(String smtpServer)
+ {
+ this.smtpServer = smtpServer;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for smtpPort
+ *******************************************************************************/
+ public String getSmtpPort()
+ {
+ return (this.smtpPort);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for smtpPort
+ *******************************************************************************/
+ public void setSmtpPort(String smtpPort)
+ {
+ this.smtpPort = smtpPort;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for smtpPort
+ *******************************************************************************/
+ public EmailMessagingProviderMetaData withSmtpPort(String smtpPort)
+ {
+ this.smtpPort = smtpPort;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/SendEmailAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/SendEmailAction.java
new file mode 100644
index 00000000..ebb67d98
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/SendEmailAction.java
@@ -0,0 +1,194 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
+
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.PartyRole;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import jakarta.mail.Address;
+import jakarta.mail.Message;
+import jakarta.mail.Session;
+import jakarta.mail.Transport;
+import jakarta.mail.internet.AddressException;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeMessage;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SendEmailAction
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException
+ {
+ EmailMessagingProviderMetaData messagingProvider = (EmailMessagingProviderMetaData) QContext.getQInstance().getMessagingProvider(sendMessageInput.getMessagingProviderName());
+
+ /////////////////////////////////////////
+ // set up properties to make a session //
+ /////////////////////////////////////////
+ Properties properties = System.getProperties();
+ properties.setProperty("mail.smtp.host", messagingProvider.getSmtpServer());
+ properties.setProperty("mail.smtp.port", messagingProvider.getSmtpPort());
+ Session session = Session.getDefaultInstance(properties);
+
+ try
+ {
+ ////////////////////////////////////////////
+ // Construct a default MimeMessage object //
+ ////////////////////////////////////////////
+ MimeMessage emailMessage = new MimeMessage(session);
+ emailMessage.setSubject(sendMessageInput.getSubject());
+ emailMessage.setText(sendMessageInput.getContentList().get(0).getBody());
+
+ Party to = sendMessageInput.getTo();
+ if(to instanceof MultiParty toMultiParty)
+ {
+ for(Party party : toMultiParty.getPartyList())
+ {
+ addRecipient(emailMessage, party);
+ }
+ }
+ else
+ {
+ addRecipient(emailMessage, to);
+ }
+
+ Party from = sendMessageInput.getTo();
+ if(from instanceof MultiParty fromMultiParty)
+ {
+ for(Party party : fromMultiParty.getPartyList())
+ {
+ addSender(emailMessage, party);
+ }
+ }
+ else
+ {
+ addSender(emailMessage, from);
+ }
+
+ /////////////
+ // send it //
+ /////////////
+ Transport.send(emailMessage);
+ System.out.println("Message dispatched successfully...");
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error sending email", e));
+ }
+
+ return null;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void addSender(MimeMessage emailMessage, Party party) throws Exception
+ {
+ if(EmailPartyRole.REPLY_TO.equals(party.getRole()))
+ {
+ InternetAddress internetAddress = getInternetAddressFromParty(party);
+ Address[] replyTo = emailMessage.getReplyTo();
+ if(replyTo == null || replyTo.length == 0)
+ {
+ emailMessage.setReplyTo(new Address[] { internetAddress });
+ }
+ else
+ {
+ List replyToList = Arrays.asList(replyTo);
+ replyToList.add(internetAddress);
+ emailMessage.setReplyTo(replyToList.toArray(new Address[0]));
+ }
+ }
+ else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.FROM.equals(party.getRole()))
+ {
+ emailMessage.setFrom(getInternetAddressFromParty(party));
+ }
+ else
+ {
+ throw (new QException("Unrecognized sender role [" + party.getRole() + "]"));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void addRecipient(MimeMessage emailMessage, Party party) throws Exception
+ {
+ Message.RecipientType recipientType;
+ if(EmailPartyRole.CC.equals(party.getRole()))
+ {
+ recipientType = Message.RecipientType.CC;
+ }
+ else if(EmailPartyRole.BCC.equals(party.getRole()))
+ {
+ recipientType = Message.RecipientType.BCC;
+ }
+ else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.TO.equals(party.getRole()))
+ {
+ recipientType = Message.RecipientType.TO;
+ }
+ else
+ {
+ throw (new QException("Unrecognized recipient role [" + party.getRole() + "]"));
+ }
+
+ InternetAddress internetAddress = getInternetAddressFromParty(party);
+ emailMessage.addRecipient(recipientType, internetAddress);
+ System.out.println("add recipient: [" + recipientType + "] => [" + internetAddress + "]");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static InternetAddress getInternetAddressFromParty(Party party) throws AddressException, UnsupportedEncodingException
+ {
+ InternetAddress internetAddress = new InternetAddress(party.getAddress());
+ if(StringUtils.hasContent(party.getLabel()))
+ {
+ internetAddress.setPersonal(party.getLabel());
+ }
+ return internetAddress;
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/messaging/MessagingProviderInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/messaging/MessagingProviderInterface.java
new file mode 100644
index 00000000..de7bdc74
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/messaging/MessagingProviderInterface.java
@@ -0,0 +1,46 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.modules.messaging;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface MessagingProviderInterface
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ String getType();
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ SendMessageOutput sendMessage(SendMessageInput sendMessageInput) throws QException;
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/messaging/QMessagingProviderDispatcher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/messaging/QMessagingProviderDispatcher.java
new file mode 100644
index 00000000..4187fb39
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/messaging/QMessagingProviderDispatcher.java
@@ -0,0 +1,123 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.modules.messaging;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
+
+
+/*******************************************************************************
+ ** This class is responsible for loading a messaging provider, by its name, and
+ ** returning an instance.
+ **
+ *******************************************************************************/
+public class QMessagingProviderDispatcher
+{
+ private static final QLogger LOG = QLogger.getLogger(QMessagingProviderDispatcher.class);
+
+ private static Map typeToProviderClassNameMap;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QMessagingProviderDispatcher()
+ {
+ initBackendTypeToModuleClassNameMap();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void initBackendTypeToModuleClassNameMap()
+ {
+ if(typeToProviderClassNameMap != null)
+ {
+ return;
+ }
+
+ Map newMap = new HashMap<>();
+
+ typeToProviderClassNameMap = newMap;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void registerMessagingProvider(MessagingProviderInterface messagingProviderInstance)
+ {
+ initBackendTypeToModuleClassNameMap();
+ String type = messagingProviderInstance.getType();
+ if(typeToProviderClassNameMap.containsKey(type))
+ {
+ LOG.info("Overwriting messagingProvider type [" + type + "] with [" + messagingProviderInstance.getClass() + "]");
+ }
+ typeToProviderClassNameMap.put(type, messagingProviderInstance.getClass().getName());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public MessagingProviderInterface getMessagingProviderInterface(QMessagingProviderMetaData messagingProviderMetaData) throws QModuleDispatchException
+ {
+ return (getMessagingProviderInterface(messagingProviderMetaData.getType()));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public MessagingProviderInterface getMessagingProviderInterface(String type) throws QModuleDispatchException
+ {
+ try
+ {
+ String className = typeToProviderClassNameMap.get(type);
+ if(className == null)
+ {
+ throw (new QModuleDispatchException("Unrecognized messaging provider type [" + type + "] in dispatcher."));
+ }
+
+ Class> moduleClass = Class.forName(className);
+ return (MessagingProviderInterface) moduleClass.getDeclaredConstructor().newInstance();
+ }
+ catch(QModuleDispatchException qmde)
+ {
+ throw (qmde);
+ }
+ catch(Exception e)
+ {
+ throw (new QModuleDispatchException("Error getting messaging provider of type: " + type, e));
+ }
+ }
+}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProviderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProviderTest.java
new file mode 100644
index 00000000..30d0fd04
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/messaging/email/EmailMessagingProviderTest.java
@@ -0,0 +1,65 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.messaging.email;
+
+
+import com.kingsrook.qqq.backend.core.BaseTest;
+import com.kingsrook.qqq.backend.core.actions.messaging.SendMessageAction;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
+import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.Test;
+
+
+/*******************************************************************************
+ ** Unit test for EmailMessagingProvider
+ *******************************************************************************/
+class EmailMessagingProviderTest extends BaseTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test() throws QException
+ {
+ new SendMessageAction().execute(new SendMessageInput()
+ .withMessagingProviderName(TestUtils.EMAIL_MESSAGING_PROVIDER_NAME)
+ .withTo(new MultiParty()
+ .withParty(new Party().withAddress("darin.kelkhoff@gmail.com").withLabel("Darin Kelkhoff").withRole(EmailPartyRole.TO))
+ .withParty(new Party().withAddress("james.maes@kingsrook.com").withLabel("Mames Maes").withRole(EmailPartyRole.CC))
+ .withParty(new Party().withAddress("tyler.samples@kingsrook.com").withLabel("Tylers Ample").withRole(EmailPartyRole.BCC))
+ )
+ // .withFrom(new Party().withAddress("darin.kelkhoff@gmail.com").withLabel("Darin Kelkhoff"))
+ .withFrom(new Party().withAddress("tim.chamberlain@kingsrook.com").withLabel("Tim Chamberlain"))
+ .withSubject("This is another qqq test message.")
+ .withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("This is a text body"))
+ .withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("This is an HTML body!"))
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
index 000afa15..3702ff4a 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
@@ -74,6 +74,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.messaging.email.EmailMessagingProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
@@ -180,6 +182,8 @@ public class TestUtils
public static final String SECURITY_KEY_TYPE_STORE_NULL_BEHAVIOR = "storeNullBehavior";
public static final String SECURITY_KEY_TYPE_INTERNAL_OR_EXTERNAL = "internalOrExternal";
+ public static final String EMAIL_MESSAGING_PROVIDER_NAME = "email";
+
public static final String SIMPLE_SCHEDULER_NAME = "simpleScheduler";
public static final String TEST_SQS_QUEUE = "testSQSQueue";
@@ -242,6 +246,8 @@ public class TestUtils
qInstance.addQueueProvider(defineSqsProvider());
qInstance.addQueue(defineTestSqsQueue());
+ qInstance.addMessagingProvider(defineEmailMessagingProvider());
+
defineWidgets(qInstance);
defineApps(qInstance);
@@ -252,6 +258,19 @@ public class TestUtils
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static QMessagingProviderMetaData defineEmailMessagingProvider()
+ {
+ return new EmailMessagingProviderMetaData()
+ .withSmtpServer("localhost")
+ .withSmtpPort("2500")
+ .withName(EMAIL_MESSAGING_PROVIDER_NAME);
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/