hazo_notify 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.cursor/rules/db_schema.mdc +0 -0
  2. package/.cursor/rules/design.mdc +16 -0
  3. package/.cursor/rules/general.mdc +49 -0
  4. package/README.md +765 -0
  5. package/components/emailer-html-editor.tsx +94 -0
  6. package/components/ui/button.tsx +53 -0
  7. package/components/ui/card.tsx +78 -0
  8. package/components/ui/input.tsx +24 -0
  9. package/components/ui/label.tsx +21 -0
  10. package/components/ui/sidebar.tsx +121 -0
  11. package/components/ui/spinner.tsx +54 -0
  12. package/components/ui/textarea.tsx +23 -0
  13. package/components/ui/tooltip.tsx +30 -0
  14. package/components.json +20 -0
  15. package/hazo_notify_config.ini +153 -0
  16. package/jest.config.js +27 -0
  17. package/jest.setup.js +1 -0
  18. package/next.config.js +22 -0
  19. package/package.json +72 -0
  20. package/postcss.config.js +6 -0
  21. package/src/app/api/hazo_notify/emailer/send/__tests__/route.test.ts +227 -0
  22. package/src/app/api/hazo_notify/emailer/send/route.ts +537 -0
  23. package/src/app/editor-00/page.tsx +47 -0
  24. package/src/app/globals.css +69 -0
  25. package/src/app/hazo_notify/emailer_test/layout.tsx +53 -0
  26. package/src/app/hazo_notify/emailer_test/page.tsx +369 -0
  27. package/src/app/hazo_notify/layout.tsx +77 -0
  28. package/src/app/hazo_notify/page.tsx +12 -0
  29. package/src/app/layout.tsx +26 -0
  30. package/src/app/page.tsx +14 -0
  31. package/src/components/blocks/editor-00/editor.tsx +61 -0
  32. package/src/components/blocks/editor-00/nodes.ts +11 -0
  33. package/src/components/blocks/editor-00/plugins.tsx +36 -0
  34. package/src/components/editor/editor-ui/content-editable.tsx +34 -0
  35. package/src/components/editor/themes/editor-theme.css +91 -0
  36. package/src/components/editor/themes/editor-theme.ts +130 -0
  37. package/src/components/ui/button.tsx +53 -0
  38. package/src/components/ui/card.tsx +78 -0
  39. package/src/components/ui/input.tsx +24 -0
  40. package/src/components/ui/label.tsx +21 -0
  41. package/src/components/ui/sidebar.tsx +121 -0
  42. package/src/components/ui/spinner.tsx +54 -0
  43. package/src/components/ui/textarea.tsx +23 -0
  44. package/src/components/ui/tooltip.tsx +30 -0
  45. package/src/lib/emailer/__tests__/emailer.test.ts +200 -0
  46. package/src/lib/emailer/emailer.ts +263 -0
  47. package/src/lib/emailer/index.ts +11 -0
  48. package/src/lib/emailer/providers/__tests__/zeptomail_provider.test.ts +196 -0
  49. package/src/lib/emailer/providers/index.ts +33 -0
  50. package/src/lib/emailer/providers/pop3_provider.ts +30 -0
  51. package/src/lib/emailer/providers/smtp_provider.ts +30 -0
  52. package/src/lib/emailer/providers/zeptomail_provider.ts +299 -0
  53. package/src/lib/emailer/types.ts +119 -0
  54. package/src/lib/emailer/utils/constants.ts +24 -0
  55. package/src/lib/emailer/utils/index.ts +9 -0
  56. package/src/lib/emailer/utils/logger.ts +71 -0
  57. package/src/lib/emailer/utils/validation.ts +84 -0
  58. package/src/lib/index.ts +6 -0
  59. package/src/lib/utils.ts +6 -0
  60. package/tailwind.config.ts +65 -0
  61. package/tsconfig.json +27 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Custom HTML editor component for emailer test page
3
+ * Wraps the Lexical editor with HTML extraction functionality
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import { SerializedEditorState, EditorState } from 'lexical';
9
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
10
+ import { $generateHtmlFromNodes } from '@lexical/html';
11
+ import { useEffect } from 'react';
12
+ import {
13
+ InitialConfigType,
14
+ LexicalComposer,
15
+ } from '@lexical/react/LexicalComposer';
16
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
17
+ import { editorTheme } from '@/components/editor/themes/editor-theme';
18
+ import { TooltipProvider } from '@/components/ui/tooltip';
19
+ import { nodes } from '@/components/blocks/editor-00/nodes';
20
+ import { Plugins } from '@/components/blocks/editor-00/plugins';
21
+
22
+ const editorConfig: InitialConfigType = {
23
+ namespace: 'EmailerEditor',
24
+ theme: editorTheme,
25
+ nodes,
26
+ onError: (error: Error) => {
27
+ console.error(error);
28
+ },
29
+ };
30
+
31
+ /**
32
+ * Plugin component to extract HTML from editor
33
+ * Must be used inside LexicalComposer context
34
+ */
35
+ function EditorHtmlExtractorPlugin({
36
+ onHtmlChange
37
+ }: {
38
+ onHtmlChange: (html: string) => void
39
+ }) {
40
+ const [editor] = useLexicalComposerContext();
41
+
42
+ useEffect(() => {
43
+ // Update HTML whenever editor state changes
44
+ const unregister = editor.registerUpdateListener(({ editorState }) => {
45
+ editorState.read(() => {
46
+ const html_string = $generateHtmlFromNodes(editor, null);
47
+ onHtmlChange(html_string);
48
+ });
49
+ });
50
+
51
+ return () => {
52
+ unregister();
53
+ };
54
+ }, [editor, onHtmlChange]);
55
+
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Custom editor component that includes HTML extraction
61
+ */
62
+ export function EmailerHtmlEditor({
63
+ editorSerializedState,
64
+ onSerializedChange,
65
+ onHtmlChange
66
+ }: {
67
+ editorSerializedState?: SerializedEditorState;
68
+ onSerializedChange?: (value: SerializedEditorState) => void;
69
+ onHtmlChange: (html: string) => void;
70
+ }) {
71
+ return (
72
+ <div className="cls_emailer_html_editor bg-background overflow-hidden rounded-lg border shadow">
73
+ <LexicalComposer
74
+ initialConfig={{
75
+ ...editorConfig,
76
+ ...(editorSerializedState
77
+ ? { editorState: JSON.stringify(editorSerializedState) }
78
+ : {}),
79
+ }}
80
+ >
81
+ <TooltipProvider>
82
+ <Plugins />
83
+ <EditorHtmlExtractorPlugin onHtmlChange={onHtmlChange} />
84
+ <OnChangePlugin
85
+ ignoreSelectionChange={true}
86
+ onChange={(editorState: EditorState) => {
87
+ onSerializedChange?.(editorState.toJSON());
88
+ }}
89
+ />
90
+ </TooltipProvider>
91
+ </LexicalComposer>
92
+ </div>
93
+ );
94
+ }
@@ -0,0 +1,53 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const button_variants = cva(
6
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
11
+ destructive:
12
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
13
+ outline:
14
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
15
+ secondary:
16
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
17
+ ghost: "hover:bg-accent hover:text-accent-foreground",
18
+ link: "text-primary underline-offset-4 hover:underline",
19
+ },
20
+ size: {
21
+ default: "h-10 px-4 py-2",
22
+ sm: "h-9 rounded-md px-3",
23
+ lg: "h-11 rounded-md px-8",
24
+ icon: "h-10 w-10",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: "default",
29
+ size: "default",
30
+ },
31
+ }
32
+ )
33
+
34
+ export interface ButtonProps
35
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
36
+ VariantProps<typeof button_variants> {
37
+ asChild?: boolean
38
+ }
39
+
40
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
41
+ ({ className, variant, size, ...props }, ref) => {
42
+ return (
43
+ <button
44
+ className={cn(button_variants({ variant, size, className }))}
45
+ ref={ref}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+ )
51
+ Button.displayName = "Button"
52
+
53
+ export { Button, button_variants }
@@ -0,0 +1,78 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ const Card = React.forwardRef<
5
+ HTMLDivElement,
6
+ React.HTMLAttributes<HTMLDivElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div
9
+ ref={ref}
10
+ className={cn(
11
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ ))
17
+ Card.displayName = "Card"
18
+
19
+ const CardHeader = React.forwardRef<
20
+ HTMLDivElement,
21
+ React.HTMLAttributes<HTMLDivElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
26
+ {...props}
27
+ />
28
+ ))
29
+ CardHeader.displayName = "CardHeader"
30
+
31
+ const CardTitle = React.forwardRef<
32
+ HTMLParagraphElement,
33
+ React.HTMLAttributes<HTMLHeadingElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <h3
36
+ ref={ref}
37
+ className={cn(
38
+ "text-2xl font-semibold leading-none tracking-tight",
39
+ className
40
+ )}
41
+ {...props}
42
+ />
43
+ ))
44
+ CardTitle.displayName = "CardTitle"
45
+
46
+ const CardDescription = React.forwardRef<
47
+ HTMLParagraphElement,
48
+ React.HTMLAttributes<HTMLParagraphElement>
49
+ >(({ className, ...props }, ref) => (
50
+ <p
51
+ ref={ref}
52
+ className={cn("text-sm text-muted-foreground", className)}
53
+ {...props}
54
+ />
55
+ ))
56
+ CardDescription.displayName = "CardDescription"
57
+
58
+ const CardContent = React.forwardRef<
59
+ HTMLDivElement,
60
+ React.HTMLAttributes<HTMLDivElement>
61
+ >(({ className, ...props }, ref) => (
62
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
63
+ ))
64
+ CardContent.displayName = "CardContent"
65
+
66
+ const CardFooter = React.forwardRef<
67
+ HTMLDivElement,
68
+ React.HTMLAttributes<HTMLDivElement>
69
+ >(({ className, ...props }, ref) => (
70
+ <div
71
+ ref={ref}
72
+ className={cn("flex items-center p-6 pt-0", className)}
73
+ {...props}
74
+ />
75
+ ))
76
+ CardFooter.displayName = "CardFooter"
77
+
78
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export interface InputProps
5
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
6
+
7
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
8
+ ({ className, type, ...props }, ref) => {
9
+ return (
10
+ <input
11
+ type={type}
12
+ className={cn(
13
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
14
+ className
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+ )
22
+ Input.displayName = "Input"
23
+
24
+ export { Input }
@@ -0,0 +1,21 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export interface LabelProps
5
+ extends React.LabelHTMLAttributes<HTMLLabelElement> {}
6
+
7
+ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
8
+ ({ className, ...props }, ref) => (
9
+ <label
10
+ ref={ref}
11
+ className={cn(
12
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ )
18
+ )
19
+ Label.displayName = "Label"
20
+
21
+ export { Label }
@@ -0,0 +1,121 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ children: React.ReactNode
6
+ }
7
+
8
+ const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
9
+ ({ className, children, ...props }, ref) => (
10
+ <div
11
+ ref={ref}
12
+ className={cn(
13
+ "flex h-screen w-64 flex-col border-r bg-card text-card-foreground",
14
+ className
15
+ )}
16
+ {...props}
17
+ >
18
+ {children}
19
+ </div>
20
+ )
21
+ )
22
+ Sidebar.displayName = "Sidebar"
23
+
24
+ const SidebarHeader = React.forwardRef<
25
+ HTMLDivElement,
26
+ React.HTMLAttributes<HTMLDivElement>
27
+ >(({ className, ...props }, ref) => (
28
+ <div
29
+ ref={ref}
30
+ className={cn("flex h-16 items-center border-b px-6", className)}
31
+ {...props}
32
+ />
33
+ ))
34
+ SidebarHeader.displayName = "SidebarHeader"
35
+
36
+ const SidebarContent = React.forwardRef<
37
+ HTMLDivElement,
38
+ React.HTMLAttributes<HTMLDivElement>
39
+ >(({ className, ...props }, ref) => (
40
+ <div
41
+ ref={ref}
42
+ className={cn("flex-1 overflow-y-auto px-4 py-4", className)}
43
+ {...props}
44
+ />
45
+ ))
46
+ SidebarContent.displayName = "SidebarContent"
47
+
48
+ const SidebarFooter = React.forwardRef<
49
+ HTMLDivElement,
50
+ React.HTMLAttributes<HTMLDivElement>
51
+ >(({ className, ...props }, ref) => (
52
+ <div
53
+ ref={ref}
54
+ className={cn("border-t px-4 py-4", className)}
55
+ {...props}
56
+ />
57
+ ))
58
+ SidebarFooter.displayName = "SidebarFooter"
59
+
60
+ const SidebarNav = React.forwardRef<
61
+ HTMLNavElement,
62
+ React.HTMLAttributes<HTMLNavElement>
63
+ >(({ className, ...props }, ref) => (
64
+ <nav
65
+ ref={ref}
66
+ className={cn("space-y-1", className)}
67
+ {...props}
68
+ />
69
+ ))
70
+ SidebarNav.displayName = "SidebarNav"
71
+
72
+ const SidebarNavItem = React.forwardRef<
73
+ HTMLLIElement,
74
+ React.HTMLAttributes<HTMLLIElement>
75
+ >(({ className, ...props }, ref) => (
76
+ <li
77
+ ref={ref}
78
+ className={cn("", className)}
79
+ {...props}
80
+ />
81
+ ))
82
+ SidebarNavItem.displayName = "SidebarNavItem"
83
+
84
+ interface SidebarNavLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
85
+ active?: boolean
86
+ asChild?: boolean
87
+ }
88
+
89
+ const SidebarNavLink = React.forwardRef<HTMLAnchorElement, SidebarNavLinkProps>(
90
+ ({ className, active, asChild, children, ...props }, ref) => {
91
+ if (asChild) {
92
+ return <>{children}</>;
93
+ }
94
+ return (
95
+ <a
96
+ ref={ref}
97
+ className={cn(
98
+ "flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
99
+ active
100
+ ? "bg-accent text-accent-foreground"
101
+ : "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
102
+ className
103
+ )}
104
+ {...props}
105
+ >
106
+ {children}
107
+ </a>
108
+ );
109
+ }
110
+ )
111
+ SidebarNavLink.displayName = "SidebarNavLink"
112
+
113
+ export {
114
+ Sidebar,
115
+ SidebarHeader,
116
+ SidebarContent,
117
+ SidebarFooter,
118
+ SidebarNav,
119
+ SidebarNavItem,
120
+ SidebarNavLink,
121
+ }
@@ -0,0 +1,54 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ size?: "sm" | "md" | "lg"
6
+ }
7
+
8
+ const Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(
9
+ ({ className, size = "md", ...props }, ref) => {
10
+ const size_classes = {
11
+ sm: "h-4 w-4",
12
+ md: "h-8 w-8",
13
+ lg: "h-12 w-12",
14
+ }
15
+
16
+ return (
17
+ <div
18
+ ref={ref}
19
+ className={cn("inline-block", className)}
20
+ {...props}
21
+ role="status"
22
+ aria-label="Loading"
23
+ >
24
+ <svg
25
+ className={cn(
26
+ "animate-spin text-primary",
27
+ size_classes[size]
28
+ )}
29
+ xmlns="http://www.w3.org/2000/svg"
30
+ fill="none"
31
+ viewBox="0 0 24 24"
32
+ >
33
+ <circle
34
+ className="opacity-25"
35
+ cx="12"
36
+ cy="12"
37
+ r="10"
38
+ stroke="currentColor"
39
+ strokeWidth="4"
40
+ />
41
+ <path
42
+ className="opacity-75"
43
+ fill="currentColor"
44
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
45
+ />
46
+ </svg>
47
+ <span className="sr-only">Loading...</span>
48
+ </div>
49
+ )
50
+ }
51
+ )
52
+ Spinner.displayName = "Spinner"
53
+
54
+ export { Spinner }
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export interface TextareaProps
5
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+ )
21
+ Textarea.displayName = "Textarea"
22
+
23
+ export { Textarea }
@@ -0,0 +1,30 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const TooltipProvider = TooltipPrimitive.Provider
9
+
10
+ const Tooltip = TooltipPrimitive.Root
11
+
12
+ const TooltipTrigger = TooltipPrimitive.Trigger
13
+
14
+ const TooltipContent = React.forwardRef<
15
+ React.ElementRef<typeof TooltipPrimitive.Content>,
16
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
17
+ >(({ className, sideOffset = 4, ...props }, ref) => (
18
+ <TooltipPrimitive.Content
19
+ ref={ref}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
29
+
30
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "slate",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ }
20
+ }
@@ -0,0 +1,153 @@
1
+ [emailer]
2
+ # Emailer module: zeptoemail_api, smtp, pop3
3
+ # Required: Yes
4
+ # Default: zeptoemail_api
5
+ # Description: Select the emailer module to use
6
+ emailer_module=zeptoemail_api
7
+
8
+ # Zeptomail API Provider Configuration
9
+ # Required when emailer_module=zeptoemail_api
10
+ # Description: API endpoint for Zeptomail email service
11
+ zeptomail_api_endpoint=https://api.zeptomail.com.au/v1.1/email
12
+
13
+ # Required when emailer_module=zeptoemail_api
14
+ # Description: Zeptomail API key for authentication
15
+ # SECURITY: Store this in .env.local file as ZEPTOMAIL_API_KEY (recommended)
16
+ # Get this from your Zeptomail account dashboard
17
+ # If not set in .env.local, you can set it here (not recommended for production)
18
+ # zeptomail_api_key=your_zeptomail_api_key
19
+
20
+ # Required: Yes
21
+ # Description: Default sender email address
22
+ # This email must be verified in your Zeptomail account
23
+ from_email=noreply@bookmemento.com
24
+
25
+ # Required: Yes
26
+ # Description: Default sender name displayed in email clients
27
+ from_name=Hazo Notify
28
+
29
+ # Optional: Reply-to email address
30
+ # Description: Email address to use for replies (if different from from_email)
31
+ # Default: Uses from_email if not specified
32
+ # reply_to_email=support@example.com
33
+
34
+ # Optional: Bounce handling email
35
+ # Description: Email address to receive bounce notifications
36
+ # bounce_email=bounce@example.com
37
+
38
+ # Optional: Return path email
39
+ # Description: Email address to use for return path
40
+ # return_path_email=returns@example.com
41
+
42
+ # SMTP Provider Configuration (placeholder - not yet implemented)
43
+ # Required when emailer_module=smtp
44
+ # Description: SMTP server hostname
45
+ # smtp_host=smtp.example.com
46
+
47
+ # Required when emailer_module=smtp
48
+ # Description: SMTP server port (typically 587 for TLS, 465 for SSL, 25 for non-secure)
49
+ # smtp_port=587
50
+
51
+ # Required when emailer_module=smtp
52
+ # Description: Use secure connection (true for SSL/TLS, false for non-secure)
53
+ # smtp_secure=false
54
+
55
+ # Required when emailer_module=smtp
56
+ # Description: SMTP authentication username (usually your email address)
57
+ # smtp_auth_user=your_email@example.com
58
+
59
+ # Required when emailer_module=smtp
60
+ # Description: SMTP authentication password or app-specific password
61
+ # smtp_auth_pass=your_password
62
+
63
+ # Optional: SMTP connection timeout in milliseconds
64
+ # Description: Timeout for SMTP connection attempts
65
+ # Default: 5000
66
+ # smtp_timeout=5000
67
+
68
+ # Optional: SMTP pool configuration
69
+ # Description: Enable connection pooling for better performance
70
+ # Default: false
71
+ # smtp_pool=false
72
+
73
+ # POP3 Provider Configuration (placeholder - not yet implemented)
74
+ # Required when emailer_module=pop3
75
+ # Description: POP3 server hostname
76
+ # pop3_host=pop3.example.com
77
+
78
+ # Required when emailer_module=pop3
79
+ # Description: POP3 server port (typically 995 for SSL, 110 for non-secure)
80
+ # pop3_port=995
81
+
82
+ # Required when emailer_module=pop3
83
+ # Description: Use secure connection (true for SSL, false for non-secure)
84
+ # pop3_secure=true
85
+
86
+ # Required when emailer_module=pop3
87
+ # Description: POP3 authentication username (usually your email address)
88
+ # pop3_auth_user=your_email@example.com
89
+
90
+ # Required when emailer_module=pop3
91
+ # Description: POP3 authentication password
92
+ # pop3_auth_pass=your_password
93
+
94
+ # Optional: POP3 connection timeout in milliseconds
95
+ # Description: Timeout for POP3 connection attempts
96
+ # Default: 5000
97
+ # pop3_timeout=5000
98
+
99
+ # Rate Limiting Configuration
100
+ # Optional: Rate limit requests per minute
101
+ # Description: Maximum number of requests allowed per minute per IP address
102
+ # Default: 10
103
+ # Recommended: 10-60 for production
104
+ # rate_limit_requests=10
105
+
106
+ # Optional: Rate limit window in seconds
107
+ # Description: Time window for rate limiting
108
+ # Default: 60 (1 minute)
109
+ # rate_limit_window=60
110
+
111
+ # Attachment Limits
112
+ # Optional: Maximum attachment size in bytes
113
+ # Description: Maximum size allowed per attachment (in bytes)
114
+ # Default: 10485760 (10MB)
115
+ # Recommended: 10MB for most use cases
116
+ # max_attachment_size=10485760
117
+
118
+ # Optional: Maximum number of attachments
119
+ # Description: Maximum number of attachments allowed per email
120
+ # Default: 10
121
+ # max_attachments=10
122
+
123
+ # Request Timeout
124
+ # Optional: Request timeout in milliseconds
125
+ # Description: Timeout for API requests to email providers
126
+ # Default: 30000 (30 seconds)
127
+ # request_timeout=30000
128
+
129
+ # Input Length Limits
130
+ # Optional: Maximum subject length in characters
131
+ # Description: Maximum length for email subject line
132
+ # Default: 255 (RFC 5322 standard)
133
+ # max_subject_length=255
134
+
135
+ # Optional: Maximum body length in bytes
136
+ # Description: Maximum size for email body content (text or HTML)
137
+ # Default: 1048576 (1MB)
138
+ # max_body_length=1048576
139
+
140
+ # CORS Configuration
141
+ # Optional: CORS allowed origins
142
+ # Description: Comma-separated list of allowed origins for CORS
143
+ # Default: empty (same-origin only)
144
+ # Use "*" to allow all origins (not recommended for production)
145
+ # cors_allowed_origins=
146
+
147
+ [ui]
148
+ # Enable UI component and all routes (e.g., /hazo_notify/emailer_test)
149
+ # Required: No
150
+ # Default: false
151
+ # Description: Enable the entire UI component and all routes for testing services
152
+ # Set to true to enable the UI, false to disable it
153
+ enable_ui=true