Co-authored-by: André de Matteo <andre.matteo@accenture.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>tags/1.5.0
| @@ -339,9 +339,11 @@ PROMPT_GENERATION_MAX_TOKENS=512 | |||
| CODE_GENERATION_MAX_TOKENS=1024 | |||
| PLUGIN_BASED_TOKEN_COUNTING_ENABLED=false | |||
| # Mail configuration, support: resend, smtp | |||
| # Mail configuration, support: resend, smtp, sendgrid | |||
| MAIL_TYPE= | |||
| # If using SendGrid, use the 'from' field for authentication if necessary. | |||
| MAIL_DEFAULT_SEND_FROM=no-reply <no-reply@dify.ai> | |||
| # resend configuration | |||
| RESEND_API_KEY= | |||
| RESEND_API_URL=https://api.resend.com | |||
| # smtp configuration | |||
| @@ -351,7 +353,8 @@ SMTP_USERNAME=123 | |||
| SMTP_PASSWORD=abc | |||
| SMTP_USE_TLS=true | |||
| SMTP_OPPORTUNISTIC_TLS=false | |||
| # Sendgid configuration | |||
| SENDGRID_API_KEY= | |||
| # Sentry configuration | |||
| SENTRY_DSN= | |||
| @@ -609,7 +609,7 @@ class MailConfig(BaseSettings): | |||
| """ | |||
| MAIL_TYPE: Optional[str] = Field( | |||
| description="Email service provider type ('smtp' or 'resend'), default to None.", | |||
| description="Email service provider type ('smtp' or 'resend' or 'sendGrid), default to None.", | |||
| default=None, | |||
| ) | |||
| @@ -663,6 +663,11 @@ class MailConfig(BaseSettings): | |||
| default=50, | |||
| ) | |||
| SENDGRID_API_KEY: Optional[str] = Field( | |||
| description="API key for SendGrid service", | |||
| default=None, | |||
| ) | |||
| class RagEtlConfig(BaseSettings): | |||
| """ | |||
| @@ -54,6 +54,15 @@ class Mail: | |||
| use_tls=dify_config.SMTP_USE_TLS, | |||
| opportunistic_tls=dify_config.SMTP_OPPORTUNISTIC_TLS, | |||
| ) | |||
| case "sendgrid": | |||
| from libs.sendgrid import SendGridClient | |||
| if not dify_config.SENDGRID_API_KEY: | |||
| raise ValueError("SENDGRID_API_KEY is required for SendGrid mail type") | |||
| self._client = SendGridClient( | |||
| sendgrid_api_key=dify_config.SENDGRID_API_KEY, _from=dify_config.MAIL_DEFAULT_SEND_FROM or "" | |||
| ) | |||
| case _: | |||
| raise ValueError("Unsupported mail type {}".format(mail_type)) | |||
| @@ -0,0 +1,42 @@ | |||
| import logging | |||
| import sendgrid # type: ignore | |||
| from python_http_client.exceptions import ForbiddenError, UnauthorizedError | |||
| from sendgrid.helpers.mail import Content, Email, Mail, To # type: ignore | |||
| class SendGridClient: | |||
| def __init__(self, sendgrid_api_key: str, _from: str): | |||
| self.sendgrid_api_key = sendgrid_api_key | |||
| self._from = _from | |||
| def send(self, mail: dict): | |||
| logging.debug("Sending email with SendGrid") | |||
| try: | |||
| _to = mail["to"] | |||
| if not _to: | |||
| raise ValueError("SendGridClient: Cannot send email: recipient address is missing.") | |||
| sg = sendgrid.SendGridAPIClient(api_key=self.sendgrid_api_key) | |||
| from_email = Email(self._from) | |||
| to_email = To(_to) | |||
| subject = mail["subject"] | |||
| content = Content("text/html", mail["html"]) | |||
| mail = Mail(from_email, to_email, subject, content) | |||
| mail_json = mail.get() # type: ignore | |||
| response = sg.client.mail.send.post(request_body=mail_json) | |||
| logging.debug(response.status_code) | |||
| logging.debug(response.body) | |||
| logging.debug(response.headers) | |||
| except TimeoutError as e: | |||
| logging.exception("SendGridClient Timeout occurred while sending email") | |||
| raise | |||
| except (UnauthorizedError, ForbiddenError) as e: | |||
| logging.exception("SendGridClient Authentication failed. Verify that your credentials and the 'from") | |||
| raise | |||
| except Exception as e: | |||
| logging.exception(f"SendGridClient Unexpected error occurred while sending email to {_to}") | |||
| raise | |||
| @@ -18,4 +18,3 @@ ignore_missing_imports=True | |||
| [mypy-flask_restful.inputs] | |||
| ignore_missing_imports=True | |||
| @@ -81,6 +81,7 @@ dependencies = [ | |||
| "weave~=0.51.0", | |||
| "yarl~=1.18.3", | |||
| "webvtt-py~=0.5.1", | |||
| "sendgrid~=6.12.3", | |||
| ] | |||
| # Before adding new dependency, consider place it in | |||
| # alphabet order (a-z) and suitable group. | |||
| @@ -1266,6 +1266,7 @@ dependencies = [ | |||
| { name = "readabilipy" }, | |||
| { name = "redis", extra = ["hiredis"] }, | |||
| { name = "resend" }, | |||
| { name = "sendgrid" }, | |||
| { name = "sentry-sdk", extra = ["flask"] }, | |||
| { name = "sqlalchemy" }, | |||
| { name = "starlette" }, | |||
| @@ -1442,6 +1443,7 @@ requires-dist = [ | |||
| { name = "readabilipy", specifier = "~=0.3.0" }, | |||
| { name = "redis", extras = ["hiredis"], specifier = "~=6.1.0" }, | |||
| { name = "resend", specifier = "~=2.9.0" }, | |||
| { name = "sendgrid", specifier = "~=6.12.3" }, | |||
| { name = "sentry-sdk", extras = ["flask"], specifier = "~=2.28.0" }, | |||
| { name = "sqlalchemy", specifier = "~=2.0.29" }, | |||
| { name = "starlette", specifier = "==0.41.0" }, | |||
| @@ -1602,6 +1604,18 @@ wheels = [ | |||
| { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922 }, | |||
| ] | |||
| [[package]] | |||
| name = "ecdsa" | |||
| version = "0.19.1" | |||
| source = { registry = "https://pypi.org/simple" } | |||
| dependencies = [ | |||
| { name = "six" }, | |||
| ] | |||
| sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" } | |||
| wheels = [ | |||
| { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, | |||
| ] | |||
| [[package]] | |||
| name = "elastic-transport" | |||
| version = "8.17.1" | |||
| @@ -4597,6 +4611,15 @@ wheels = [ | |||
| { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, | |||
| ] | |||
| [[package]] | |||
| name = "python-http-client" | |||
| version = "3.3.7" | |||
| source = { registry = "https://pypi.org/simple" } | |||
| sdist = { url = "https://files.pythonhosted.org/packages/56/fa/284e52a8c6dcbe25671f02d217bf2f85660db940088faf18ae7a05e97313/python_http_client-3.3.7.tar.gz", hash = "sha256:bf841ee45262747e00dec7ee9971dfb8c7d83083f5713596488d67739170cea0", size = 9377, upload-time = "2022-03-09T20:23:56.386Z" } | |||
| wheels = [ | |||
| { url = "https://files.pythonhosted.org/packages/29/31/9b360138f4e4035ee9dac4fe1132b6437bd05751aaf1db2a2d83dc45db5f/python_http_client-3.3.7-py3-none-any.whl", hash = "sha256:ad371d2bbedc6ea15c26179c6222a78bc9308d272435ddf1d5c84f068f249a36", size = 8352, upload-time = "2022-03-09T20:23:54.862Z" }, | |||
| ] | |||
| [[package]] | |||
| name = "python-iso639" | |||
| version = "2025.2.18" | |||
| @@ -5071,6 +5094,20 @@ wheels = [ | |||
| { url = "https://files.pythonhosted.org/packages/6c/42/cd8dc81f8060de1f14960885ad5b2d2651f41de8b93d09f3f919d6567a5a/scipy_stubs-1.15.3.0-py3-none-any.whl", hash = "sha256:a251254cf4fd6e7fb87c55c1feee92d32ddbc1f542ecdf6a0159cdb81c2fb62d", size = 459062 }, | |||
| ] | |||
| [[package]] | |||
| name = "sendgrid" | |||
| version = "6.12.4" | |||
| source = { registry = "https://pypi.org/simple" } | |||
| dependencies = [ | |||
| { name = "ecdsa" }, | |||
| { name = "python-http-client" }, | |||
| { name = "werkzeug" }, | |||
| ] | |||
| sdist = { url = "https://files.pythonhosted.org/packages/11/31/62e00433878dccf33edf07f8efa417b9030a2464eb3b04bbd797a11b4447/sendgrid-6.12.4.tar.gz", hash = "sha256:9e88b849daf0fa4bdf256c3b5da9f5a3272402c0c2fd6b1928c9de440db0a03d", size = 50271, upload-time = "2025-06-12T10:29:37.213Z" } | |||
| wheels = [ | |||
| { url = "https://files.pythonhosted.org/packages/c2/9c/45d068fd831a65e6ed1e2ab3233de58784842afdc62fdcdd0a01bbb6b39d/sendgrid-6.12.4-py3-none-any.whl", hash = "sha256:9a211b96241e63bd5b9ed9afcc8608f4bcac426e4a319b3920ab877c8426e92c", size = 102122, upload-time = "2025-06-12T10:29:35.457Z" }, | |||
| ] | |||
| [[package]] | |||
| name = "sentry-sdk" | |||
| version = "2.28.0" | |||
| @@ -726,10 +726,11 @@ NOTION_INTERNAL_SECRET= | |||
| # Mail related configuration | |||
| # ------------------------------ | |||
| # Mail type, support: resend, smtp | |||
| # Mail type, support: resend, smtp, sendgrid | |||
| MAIL_TYPE=resend | |||
| # Default send from email address, if not specified | |||
| # If using SendGrid, use the 'from' field for authentication if necessary. | |||
| MAIL_DEFAULT_SEND_FROM= | |||
| # API-Key for the Resend email provider, used when MAIL_TYPE is `resend`. | |||
| @@ -745,6 +746,9 @@ SMTP_PASSWORD= | |||
| SMTP_USE_TLS=true | |||
| SMTP_OPPORTUNISTIC_TLS=false | |||
| # Sendgid configuration | |||
| SENDGRID_API_KEY= | |||
| # ------------------------------ | |||
| # Others Configuration | |||
| # ------------------------------ | |||
| @@ -327,6 +327,7 @@ x-shared-env: &shared-api-worker-env | |||
| SMTP_PASSWORD: ${SMTP_PASSWORD:-} | |||
| SMTP_USE_TLS: ${SMTP_USE_TLS:-true} | |||
| SMTP_OPPORTUNISTIC_TLS: ${SMTP_OPPORTUNISTIC_TLS:-false} | |||
| SENDGRID_API_KEY: ${SENDGRID_API_KEY:-} | |||
| INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-4000} | |||
| INVITE_EXPIRY_HOURS: ${INVITE_EXPIRY_HOURS:-72} | |||
| RESET_PASSWORD_TOKEN_EXPIRY_MINUTES: ${RESET_PASSWORD_TOKEN_EXPIRY_MINUTES:-5} | |||