• Issue

    I’d like to relay all my Ubuntu 22.04 email through Amazon SES.

    FYI

    Use at your own risk. Like seriously, do some research prior to implementing any of this into your own environment. Consider factors like costs, scalability, etc.

    Solution

    To relay all your email through Amazon SES (Simple Email Service) on an Ubuntu 22.04 server, you will need to:

    1. Set up an Amazon SES account and verify your email/domain.
    2. Install and configure Postfix to relay email through SES.
    3. Ensure proper authentication and security configurations.

    Here are the detailed steps:

    1. Set up Amazon SES

    Sign up for AWS and SES

    1. Sign in to AWS: If you don’t already have an AWS account, create one at AWS Sign-Up.
    2. Navigate to SES: Go to the SES dashboard in the AWS Management Console.

    Verify Email/Domain

    1. Verify an Email Address:
      • Go to the SES console.
      • In the left pane, click “Email Addresses”.
      • Click “Verify a New Email Address”.
      • Enter your email address and click “Verify This Email Address”.
      • Check your email and follow the verification link.
    2. Verify a Domain (recommended for sending from multiple addresses):
      • Go to the SES console.
      • In the left pane, click “Domains”.
      • Click “Verify a New Domain”.
      • Enter your domain name.
      • Follow the instructions to add a DNS record to your domain’s DNS settings.

    Obtain SMTP Credentials

    1. Create SMTP Credentials:
      • Go to the SES console.
      • In the left pane, click “SMTP Settings”.
      • Click “Create My SMTP Credentials”.
      • Follow the prompts to create a new IAM user with SES permissions.
      • Download or copy the SMTP credentials (SMTP username and password).

    2. Install and Configure Postfix

    Install Postfix

    Open a terminal on your Ubuntu server and install Postfix:

    sudo apt update
    sudo apt install postfix

    During installation, choose “Internet Site” and set the system mail name to your domain name (e.g., example.com).

    Configure Postfix

    Edit the Postfix configuration file:

    sudo nano /etc/postfix/main.cf

    Add or modify the following lines to configure Postfix to use Amazon SES as a relay host:

    relayhost = [email-smtp.us-east-1.amazonaws.com]:587
    smtp_sasl_auth_enable = yes
    smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
    smtp_sasl_security_options = noanonymous
    smtp_tls_security_level = encrypt
    smtp_tls_note_starttls_offer = yes

    Replace email-smtp.us-east-1.amazonaws.com with the correct SES SMTP endpoint for your region. You can find the list of SMTP endpoints in the Amazon SES documentation.

    Create the SASL Password File

    Create and edit the SASL password file:

    sudo nano /etc/postfix/sasl_passwd

    Add the following line, replacing the SMTP endpoint, username, and password with your SES SMTP details:

    [email-smtp.us-east-1.amazonaws.com]:587 YOUR_SES_SMTP_USERNAME:YOUR_SES_SMTP_PASSWORD

    Secure the file permissions:

    sudo chmod 600 /etc/postfix/sasl_passwd
    sudo postmap /etc/postfix/sasl_passwd

    Restart Postfix

    Restart the Postfix service to apply the changes:

    sudo systemctl restart postfix

    3. Ensure Proper Authentication and Security

    SPF and DKIM

    1. SPF: Add an SPF record to your DNS to authorize Amazon SES to send emails on behalf of your domain. Example SPF record:
       v=spf1 include:amazonses.com ~all
    1. DKIM: Enable DKIM in the SES console for your domain to sign your emails. Follow the instructions in the SES console to add the necessary DNS records.

    Testing

    Send a test email to ensure everything is configured correctly:

    echo "Test email body" | mail -s "Test email subject" your-email@example.com

    Check the recipient’s inbox and the /var/log/mail.log file on your server for any errors.

    Summary

    1. Sign up for Amazon SES and verify your email or domain.
    2. Install and configure Postfix to relay through SES.
    3. Ensure SPF and DKIM are properly set up.

    This setup ensures your emails are securely relayed through Amazon SES, leveraging its robust infrastructure.

    Disclaimer

    The information provided in this blog post is for educational and informational purposes only. The steps and instructions are based on personal experience and research, and are intended to help users configure email relaying through Amazon SES on Ubuntu 22.04.

    No Warranty: The author and publisher make no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability, or availability with respect to the information, products, services, or related graphics contained in this blog post for any purpose. Any reliance you place on such information is therefore strictly at your own risk.

    Limitation of Liability: In no event will the author or publisher be liable for any loss or damage including without limitation, indirect or consequential loss or damage, or any loss or damage whatsoever arising from loss of data or profits arising out of, or in connection with, the use of this blog post.

    External Links: Through this blog post, you are able to link to other websites which are not under the control of the author. We have no control over the nature, content, and availability of those sites. The inclusion of any links does not necessarily imply a recommendation or endorse the views expressed within them.

    By using the information in this blog post, you agree to the terms of this disclaimer. If you do not agree to these terms, please do not use the information provided.


  • In JavaScript, .contains() and .includes() are used for different purposes:

    .contains(): This is a method of the Node interface in the Document Object Model (DOM). It’s used to check whether a node is a descendant of a given node, i.e., child, grandchild, etc.

    node.contains(otherNode)

    .includes(): This is a method of the Array and String prototypes in JavaScript. It’s used to determine whether an array includes a certain value among its entries or whether a string includes a certain substring.

    For arrays:

    array.includes(valueToFind, fromIndex)

    For strings:

    string.includes(searchString, position)

    So, the main difference is that .contains() is used with DOM nodes, while .includes() is used with arrays and strings.


  • Using VS Code, go to your settings.json file and add the following within the {} brackets.

    "files.associations": {
        "*.jinja": "html",
        "*.jinja2": "html",
        "*.j2": "html",
        "*.njk": "html"
    }

    Have you tried this approach or something similar? Leave a comment if this helped or if you have another approach.


  • # String Concatenation
    name = "John"
    age = 25
    print("Hello, my name is " + name + " and I am " + str(age) + " years old.")
    
    # String Interpolation
    name = "John"
    age = 25
    print(f"Hello, my name is {name} and I am {age} years old.")
    
    # String Interpolation with format()
    name = "John"
    age = 25
    print("Hello, my name is {} and I am {} years old.".format(name, age))
    
    # String Interpolation with format() and positional arguments
    name = "John"
    age = 25
    print("Hello, my name is {0} and I am {1} years old.".format(name, age))
    
    # String Interpolation with format() and keyword arguments
    name = "John"
    age = 25
    print("Hello, my name is {name} and I am {age} years old.".format(name=name, age=age))
    
    # String Interpolation with format() and mixed arguments
    name = "John"
    age = 25
    print("Hello, my name is {0} and I am {age} years old.".format(name, age=age))
    

  • # for loops
    # About for loops
    # for loops are used to iterate over a sequence (list, tuple, string) or other 
    # iterable objects.
    
    # Syntax
    # for variable in sequence:
    #    code block
    # Examples
    
    # Iterate over a list
    # A list is a collection which is ordered and changeable.
    print("\nIterate over a list")
    fruits = ["apple", "banana", "cherry"]
    for fruit in fruits:
        print(fruit)
        # Output
        # apple
        # banana
        # cherry
    
    # Iterate over a string
    # A string is a sequence of characters
    print("\nIterate over a string")
    for letter in "apple":
        print(letter)
        # Output
        # a
        # p
        # p
        # l
        # e
    
    # Iterate over a range
    # A range is a sequence of numbers, starting from 0 by default, and increments
    print("\nIterate over a range")
    for i in range(5):
        print(i)
        # Output
        # 0
        # 1
        # 2
        # 3
        # 4
        # Note: The range() function returns a sequence of numbers, starting from 0 
        # by default, and increments by 1 (by default), and stops before a 
        # specified number.
    
    # Iterate over a tuple
    # A tuple is a collection which is ordered and unchangeable.
    print("\nIterate over a tuple")
    colors = ("red", "green", "blue")
    for color in colors:
        print(color)
        # Output
        # red
        # green
        # blue
    
    # Iterate over a dictionary
    # A dictionary is a collection which is unordered, changeable and indexed.
    print("\nIterate over a dictionary")
    person = {
        "name": "Alice",
        "age": 25,
        "is_active": True
    }
    for key in person:
        print(key, person[key])
        # Output
        # name Alice
        # age 25
        # is_active True
        # Note: The for loop iterates over the keys of the dictionary.
    
    # Iterate over a dictionary using items() method
    # The items() method returns a view object that displays a list of a given
    # dictionary's key-value tuple pair.
    print("\nIterate over a dictionary using items() method")
    for key, value in person.items():
        print(key, value)
        # Output
        # name Alice
        # age 25
        # is_active True
        # Note: The for loop iterates over the key-value pairs of the dictionary.
        if key == "age" and value >= 18:
            print(person["name"], "is an adult.")
    
    # Iterate over a dictionary using values() method
    # The values() method returns a view object that displays a list of all the
    # values in the dictionary.
    print("\nIterate over a dictionary using values() method")
    for value in person.values():
        print(value)
        # Output
        # Alice
        # 25
        # True
        # Note: The for loop iterates over the values of the dictionary.
    
    # Iterate over a dictionary using keys() method
    # The keys() method returns a view object that displays a list of all the keys
    # in the dictionary.
    print("\nIterate over a dictionary using keys() method")
    for key in person.keys():
        print(key)
        # Output
        # name
        # age
        # is_active
        # Note: The for loop iterates over the keys of the dictionary.
    
    # Iterate over a dictionary using get() method
    # The get() method returns the value of the specified key.
    print("\nIterate over a dictionary using get() method")
    print(person.get("name"))
    
    # Nested for loops
    
    # clients
    clients = [
        {"name": "Alice", "age": 25},
        {"name": "Bob", "age": 30},
        {"name": "Charlie", "age": 35}
    ]
    # products
    products = ["apple", "banana", "cherry"]
    inventory = {
        "apple": 10,
        "banana": 20,
        "cherry": 30
    }
    # orders
    orders = [
        {"client": "Alice", "product": "apple"},
        {"client": "Bob", "product": "banana"},
        {"client": "Charlie", "product": "cherry"}
    ]
    
    # Iterate over clients
    print("\nIterate over clients")
    for client in clients:
        print(client["name"], client["age"])
        # Output
        # Alice 25
        # Bob 30
        # Charlie 35
    
    # Iterate over products
    print("\nIterate over products")
    for product in products:
        print(product)
        # Output
        # apple
        # banana
        # cherry
    
    # Iterate over orders
    print("\nIterate over orders")
    for order in orders:
        print(order["client"], order["product"])
        # Output
        # Alice apple
        # Bob banana
        # Charlie cherry
    
    # Iterate over orders and products and update inventory
    print("\nIterate over orders and products and update inventory")
    for order in orders:
        for product in products:
            if order["product"] == product:
                inventory[product] -= 1
                print(order["client"], "ordered", order["product"])
                print("Inventory of", product, "is", inventory[product])
                # Output
                # Alice ordered apple
                # Inventory of apple is 9
                # Bob ordered banana
                # Inventory of banana is 19
                # Charlie ordered cherry
                # Inventory of cherry is 29
    # Note: The nested for loop iterates over the orders and products and updates
    # the inventory of the products.
    

  • -- Create a new database
    -- This creates a new database named "sandbox". This is not necessary if you already have a database to work with.
    CREATE DATABASE database_name;
    
    -- Use a specific database
    -- This selects the "sandbox" database for use. Replace "sandbox" with the name of the database you want to use.
    USE database_name;
    
    -- Create a new table
    -- This creates a new table named "table_name" with columns "column1" and "column2" of data types "datatype1" and "datatype2" respectively. Replace "table_name", "column1", "column2", "datatype1", and "datatype2" with the desired names and data types.
    CREATE TABLE table_name (
        column1 datatype,
        column2 datatype,
        ...
    );
    
    -- Insert data into a table
    -- This inserts a new row into the "table_name" table with values "value1" and "value2" for columns "column1" and "column2" respectively. Replace "table_name", "column1", "column2", "value1", and "value2" with the desired table name, column names, and values.
    INSERT INTO table_name (column1, column2, ...)
    VALUES (value1, value2, ...);
    
    -- Select all rows from a table
    -- This selects all rows from the "table_name" table. Replace "table_name" with the name of the table you want to select from.
    SELECT * FROM table_name;
    
    -- Select specific columns from a table
    -- This selects specific columns "column1" and "column2" from the "table_name" table. Replace "column1", "column2", and "table_name" with the desired column names and table name.
    SELECT column1, column2, ... FROM table_name;
    
    -- Filter rows using WHERE clause
    -- This selects all rows from the "table_name" table where the condition is met. Replace "table_name" with the name of the table you want to select from and "condition" with the desired condition.
    SELECT * FROM table_name WHERE condition;
    
    -- Update data in a table
    -- This updates the "column1" and "column2" columns in the "table_name" table with new values "value1" and "value2" where the condition is met. Replace "table_name", "column1", "column2", "value1", "value2", and "condition" with the desired table name, column names, values, and condition.
    UPDATE table_name SET column1 = value1, column2 = value2, ... WHERE condition;
    
    -- Delete data from a table
    -- This deletes rows from the "table_name" table where the condition is met. Replace "table_name" with the name of the table you want to delete from and "condition" with the desired condition.
    DELETE FROM table_name WHERE condition;
    
    -- Join two or more tables
    -- This joins the "table1" and "table2" tables on the "column" column. Replace "table1", "table2", and "column" with the names of the tables and column you want to join on.
    SELECT * FROM table1 JOIN table2 ON table1.column = table2.column;
    
    -- Order rows in ascending or descending order
    -- This selects all rows from the "table_name" table and orders them in ascending or descending order based on the "column" column. Replace "table_name" and "column" with the name of the table and column you want to order by, and "ASC" or "DESC" with the desired order.
    SELECT * FROM table_name ORDER BY column ASC/DESC;
    
    -- Limit the number of rows returned
    -- This selects the first "number" rows from the "table_name" table. Replace "table_name" with the name of the table you want to select from and "number" with the desired number of rows.
    SELECT * FROM table_name LIMIT number;
    
    -- Group rows and perform aggregate functions
    -- This groups rows in the "table_name" table by the "column" column and counts the number of rows in each group. Replace "table_name" and "column" with the name of the table and column you want to group by.
    SELECT column, COUNT(*) FROM table_name GROUP BY column;
    
    -- Create an index on a table
    -- This creates an index named "index_name" on the "column" column in the "table_name" table. Replace "index_name", "table_name", and "column" with the desired index name, table name, and column.
    CREATE INDEX index_name ON table_name (column);
    
    -- Drop a table
    -- This drops the "table_name" table. Replace "table_name" with the name of the table you want to drop. This is a destructive operation and cannot be undone.
    DROP TABLE table_name;
    
    -- Drop a database
    -- This drops the "database_name" database. Replace "database_name" with the name of the database you want to drop. This is a destructive operation and cannot be undone.
    DROP DATABASE database_name;
    
    -- Show all databases
    SHOW DATABASES;
    
    -- Show all tables in a database
    SHOW TABLES;
    
    -- Show the structure of a table
    -- This shows the columns, data types, and other information about the "table_name" table. Replace "table_name" with the name of the table you want to show the structure of.
    DESC table_name;
    
    -- Show the indexes on a table
    -- This shows the indexes on the "table_name" table. Replace "table_name" with the name of the table you want to show the indexes of.
    SHOW INDEX FROM table_name;
    
    -- Show the status of the server
    -- This shows various status information about the server, such as the number of connections, queries, and other statistics.
    SHOW STATUS;
    
    -- Show the current user
    SELECT USER();
    
    -- Show the current database
    SELECT DATABASE();
    
    -- Show the version of MySQL
    SELECT VERSION();
    
    -- Show the current date and time
    SELECT NOW();
    
    -- Show the current time zone
    SELECT @@time_zone;
    
    -- Join two or more tables
    -- This joins the "table1" and "table2" tables on the "column" column.
    -- A join combines rows from two or more tables based on a related column between them.
    SELECT * FROM table1 JOIN table2 ON table1.column = table2.column;
    
    -- Left join two or more tables
    -- This left joins the "table1" and "table2" tables on the "column" column.
    -- A left join returns all rows from the left table and the matched rows from the right table.
    SELECT * FROM table1 LEFT JOIN table2 ON table1.column = table2.column;
    
    -- Right join two or more tables
    -- This right joins the "table1" and "table2" tables on the "column" column.
    -- A right join returns all rows from the right table and the matched rows from the left table.
    SELECT * FROM table1 RIGHT JOIN table2 ON table1.column = table2.column;
    
    -- Full outer join two or more tables
    -- This full outer joins the "table1" and "table2" tables on the "column" column.
    -- A full outer join returns all rows when there is a match in either the left or right table.
    SELECT * FROM table1 FULL OUTER JOIN table2 ON table1.column = table2.column;
    
    -- Cross join two or more tables
    -- This cross joins the "table1" and "table2" tables.
    -- A cross join returns the Cartesian product of the two tables, i.e., all possible combinations of rows.
    SELECT * FROM table1 CROSS JOIN table2;
    
    



  • Red Dead Redemption

    One of my absolute favorite online games…


  • While instance properties can be determined at launch, some of them can be updated after the instance has been created. Specifically, an instance’s memory, disk space, and the number of its CPUs are exposed via daemon settings: local..(cpus|disk|memory).

    Documentation